<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.0">Jekyll</generator><link href="https://www.crossingtheruby.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.crossingtheruby.com/" rel="alternate" type="text/html" /><updated>2025-04-20T18:32:09+00:00</updated><id>https://www.crossingtheruby.com/feed.xml</id><title type="html">crossingtheruby.com</title><subtitle>Iacta alea est</subtitle><author><name>Philip Poots</name></author><entry><title type="html">HTML Parsley with Herb</title><link href="https://www.crossingtheruby.com/2025/04/20/html-parsley-with-herb.html" rel="alternate" type="text/html" title="HTML Parsley with Herb" /><published>2025-04-20T00:00:00+00:00</published><updated>2025-04-20T00:00:00+00:00</updated><id>https://www.crossingtheruby.com/2025/04/20/html-parsley-with-herb</id><content type="html" xml:base="https://www.crossingtheruby.com/2025/04/20/html-parsley-with-herb.html">&lt;p&gt;Herb, the new HTML-aware ERB parser library by Marco Roth, parses ERB templates into an abstract syntax tree. The tree has two main branches:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;HTML&lt;/li&gt;
  &lt;li&gt;Embedded Ruby&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the real world these are extricably (thanks Herb!) intertwined but for now I’d like to concentrate on the HTML tree structure that the Herb AST uses.&lt;/p&gt;

&lt;h2 id=&quot;herb-ast-node-types&quot;&gt;Herb AST Node Types&lt;/h2&gt;

&lt;p&gt;The docs on Herb’s Parser are quite, &lt;em&gt;ahem&lt;/em&gt;, sparse, so I had to do some introspection to arrive at a list of nodes in the tree:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;require &quot;herb&quot;

Herb.version
#=&amp;gt; &quot;herb gem v0.1.0, libprism v1.4.0, libherb v0.1.0 (Ruby C native extension)&quot;

constants = Herb::AST.constants.map do |const|
  klass = Herb::AST.const_get(const)
  next unless klass.is_a?(Class)
  klass.name
end.compact.each do |klass|
  puts klass
end; constants.count
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This produces a list of &lt;code&gt;Herb::AST::Node&lt;/code&gt;s, which after a touch of light editing to order and group the twenty-eight nodes, looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Herb::AST::Node
Herb::AST::DocumentNode

Herb::AST::HTMLDoctypeNode
Herb::AST::HTMLCommentNode
Herb::AST::HTMLElementNode
Herb::AST::HTMLOpenTagNode
Herb::AST::HTMLAttributeNode
Herb::AST::HTMLAttributeNameNode
Herb::AST::HTMLAttributeValueNode
Herb::AST::LiteralNode
Herb::AST::HTMLTextNode
Herb::AST::HTMLCloseTagNode
Herb::AST::HTMLSelfCloseTagNode

Herb::AST::WhitespaceNode

Herb::AST::ERBContentNode
Herb::AST::ERBIfNode
Herb::AST::ERBUnlessNode
Herb::AST::ERBElseNode
Herb::AST::ERBBlockNode
Herb::AST::ERBCaseNode
Herb::AST::ERBWhenNode
Herb::AST::ERBForNode
Herb::AST::ERBWhileNode
Herb::AST::ERBUntilNode
Herb::AST::ERBBeginNode
Herb::AST::ERBRescueNode
Herb::AST::ERBEnsureNode
Herb::AST::ERBEndNode
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The two groups are distinguished here, with the exception of the mutual parents (&lt;code&gt;DocumentNode&lt;/code&gt;—parent (or root) of the syntax tree, &lt;code&gt;Node&lt;/code&gt;—parent of the class hierarchy) and the shared &lt;code&gt;WhitespaceNode&lt;/code&gt; rather appropriately surrounded by, em, whitespace.&lt;/p&gt;

&lt;h2 id=&quot;parsing-html&quot;&gt;Parsing HTML&lt;/h2&gt;

&lt;h3 id=&quot;herbastdocumentnode&quot;&gt;&lt;code&gt;Herb::AST::DocumentNode&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;The ever-present root.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Herb.parse(&quot;&quot;).value
=&amp;gt; 
@ DocumentNode (location: (1:0)-(1:0))
└── children: []
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Contains an array of child nodes in &lt;code&gt;DocumentNode#children&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;herbasthtmldoctypenode&quot;&gt;&lt;code&gt;Herb::AST::HTMLDoctypeNode&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;The singular doctype declaration. Also note the &lt;code&gt;Herb::AST::LiteralNode&lt;/code&gt;, a node representing some literal text (as distinct from HTML text) usually inside an HTML doctype, HTML comment, or HTML attribute value.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Herb.parse(&quot;&amp;lt;!doctype html&amp;gt;&quot;).value
=&amp;gt; 
@ DocumentNode (location: (1:0)-(1:15))
└── children: (1 item)
    └── @ HTMLDoctypeNode (location: (1:0)-(1:15))
        ├── tag_opening: &quot;&amp;lt;!doctype&quot; (location: (1:0)-(1:9))
        ├── children: (1 item)
        │   └── @ LiteralNode (location: (1:9)-(1:14))
        │       └── content: &quot; html&quot;
        │
        └── tag_closing: &quot;&amp;gt;&quot; (location: (1:14)-(1:15))
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;herbasthtmlcommentnode&quot;&gt;&lt;code&gt;Herb::AST::HTMLCommentNode&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;The humble HTML comment.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Herb.parse(&quot;&amp;lt;!-- comment --&amp;gt;&quot;).value
=&amp;gt; 
@ DocumentNode (location: (1:0)-(1:16))
└── children: (1 item)
    └── @ HTMLCommentNode (location: (1:0)-(1:16))
        ├── comment_start: &quot;&amp;lt;!--&quot; (location: (1:0)-(1:4))
        ├── children: (1 item)
        │   └── @ LiteralNode (location: (1:4)-(1:13))
        │       └── content: &quot; comment &quot;
        │
        └── comment_end: &quot;--&amp;gt;&quot; (location: (1:13)-(1:16))
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;herbasthtmlelementnode&quot;&gt;&lt;code&gt;Herb::AST::HTMLElementNode&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;HTMLElementNode&lt;/code&gt; captures the full element from opening tag to closing tag, including the contents (&lt;code&gt;body&lt;/code&gt;). Each of these are represented by a dedicated node:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;HTMLElementNode#open_tag  =&amp;gt; HTMLOpenTagNode&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;HTMLElementNode#body      =&amp;gt; [HTMLTextNode, …]&lt;/code&gt; (array of child nodes)&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;HTMLElementNode#close_tag =&amp;gt; HTMLCloseTagNode&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;pre&gt;&lt;code&gt;Herb.parse(&quot;&amp;lt;em&amp;gt;emphasis&amp;lt;/em&amp;gt;&quot;).value
=&amp;gt;
@ DocumentNode (location: (1:0)-(1:17))
└── children: (1 item)
    └── @ HTMLElementNode (location: (1:0)-(1:17))
        ├── open_tag:
        │   └── @ HTMLOpenTagNode (location: (1:0)-(1:4))
        │       ├── tag_opening: &quot;&amp;lt;&quot; (location: (1:0)-(1:1))
        │       ├── tag_name: &quot;em&quot; (location: (1:1)-(1:3))
        │       ├── tag_closing: &quot;&amp;gt;&quot; (location: (1:3)-(1:4))
        │       ├── children: []
        │       └── is_void: false
        │
        ├── tag_name: &quot;em&quot; (location: (1:1)-(1:3))
        ├── body: (1 item)
        │   └── @ HTMLTextNode (location: (1:4)-(1:12))
        │       └── content: &quot;emphasis&quot;
        │
        ├── close_tag:
        │   └── @ HTMLCloseTagNode (location: (1:12)-(1:17))
        │       ├── tag_opening: &quot;&amp;lt;/&quot; (location: (1:12)-(1:14))
        │       ├── tag_name: &quot;em&quot; (location: (1:14)-(1:16))
        │       └── tag_closing: &quot;&amp;gt;&quot; (location: (1:16)-(1:17))
        │
        └── is_void: false
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;herbasthtmlattributenode-herbasthtmlattributenamenode-herbasthtmlattributevaluenode&quot;&gt;&lt;code&gt;Herb::AST::HTMLAttributeNode&lt;/code&gt;, &lt;code&gt;Herb::AST::HTMLAttributeNameNode&lt;/code&gt;, &lt;code&gt;Herb::AST::HTMLAttributeValueNode&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;HTMLOpenTagNode#children&lt;/code&gt; can also contain an array of HTML attributes, represented by the &lt;code&gt;HTMLAttributeNode&lt;/code&gt;. The &lt;code&gt;HTMLAttributeNode&lt;/code&gt; has a &lt;code&gt;#name&lt;/code&gt; pointing to an &lt;code&gt;HTMLAttributeNameNode&lt;/code&gt; and a &lt;code&gt;#value&lt;/code&gt; pointing to the &lt;code&gt;HTMLAttributeValueNode&lt;/code&gt;. The &lt;code&gt;HTMLAttributeValueNode#children&lt;/code&gt; points to an array of &lt;code&gt;LiteralNode&lt;/code&gt;s.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Herb.parse('&amp;lt;span class=&quot;alert&quot;&amp;gt;Watch out!&amp;lt;/span&amp;gt;').value
=&amp;gt;
@ DocumentNode (location: (1:0)-(1:37))
└── children: (1 item)
    └── @ HTMLElementNode (location: (1:0)-(1:37))
        ├── open_tag:
        │   └── @ HTMLOpenTagNode (location: (1:0)-(1:20))
        │       ├── tag_opening: &quot;&amp;lt;&quot; (location: (1:0)-(1:1))
        │       ├── tag_name: &quot;span&quot; (location: (1:1)-(1:5))
        │       ├── tag_closing: &quot;&amp;gt;&quot; (location: (1:19)-(1:20))
        │       ├── children: (1 item)
        │       │   └── @ HTMLAttributeNode (location: (1:6)-(1:19))
        │       │       ├── name:
        │       │       │   └── @ HTMLAttributeNameNode (location: (1:6)-(1:11))
        │       │       │       └── name: &quot;class&quot; (location: (1:6)-(1:11))
        │       │       │
        │       │       ├── equals: &quot;=&quot; (location: (1:11)-(1:12))
        │       │       └── value:
        │       │           └── @ HTMLAttributeValueNode (location: (1:12)-(1:19))
        │       │               ├── open_quote: &quot;&quot;&quot; (location: (1:12)-(1:13))
        │       │               ├── children: (1 item)
        │       │               │   └── @ LiteralNode (location: (1:13)-(1:18))
        │       │               │       └── content: &quot;alert&quot;
        │       │               │
        │       │               ├── close_quote: &quot;&quot;&quot; (location: (1:18)-(1:19))
        │       │               └── quoted: true
        │       │
        │       │
        │       └── is_void: false
        │
        ├── tag_name: &quot;span&quot; (location: (1:1)-(1:5))
        ├── body: (1 item)
        │   └── @ HTMLTextNode (location: (1:20)-(1:30))
        │       └── content: &quot;Watch out!&quot;
        │
        ├── close_tag:
        │   └── @ HTMLCloseTagNode (location: (1:30)-(1:37))
        │       ├── tag_opening: &quot;&amp;lt;/&quot; (location: (1:30)-(1:32))
        │       ├── tag_name: &quot;span&quot; (location: (1:32)-(1:36))
        │       └── tag_closing: &quot;&amp;gt;&quot; (location: (1:36)-(1:37))
        │
        └── is_void: false
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;herbasthtmlselfclosetagnode&quot;&gt;&lt;code&gt;Herb::AST::HTMLSelfCloseTagNode&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;I would expect a void HTML node to result in an &lt;code&gt;HTMLSelfCloseTagNode&lt;/code&gt;, however it seems this is instead handled by a &lt;code&gt;HTMLOpenTagNode#void == true&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Herb.parse('&amp;lt;br /&amp;gt;').value
@ DocumentNode (location: (1:0)-(1:6))
└── children: (1 item)
    └── @ HTMLElementNode (location: (1:0)-(1:6))
        ├── open_tag:
        │   └── @ HTMLOpenTagNode (location: (1:0)-(1:6))
        │       ├── tag_opening: &quot;&amp;lt;&quot; (location: (1:0)-(1:1))
        │       ├── tag_name: &quot;br&quot; (location: (1:1)-(1:3))
        │       ├── tag_closing: &quot;/&amp;gt;&quot; (location: (1:4)-(1:6))
        │       ├── children: []
        │       └── is_void: true
        │
        ├── tag_name: &quot;br&quot; (location: (1:1)-(1:3))
        ├── body: []
        ├── close_tag: ∅
        └── is_void: true
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;tree-nest&quot;&gt;Tree Nest&lt;/h2&gt;

&lt;p&gt;The following tree is nested according to the levels above for an quick overview. The only thing to note is that where the &lt;code&gt;HTMLElementNode#body&lt;/code&gt; in this case contains an “only-child” of &lt;code&gt;HTMLTextNode&lt;/code&gt; for simplicity, it could of course contain many children, of different types, and be nested several layers deep depending on the structure of the HTML.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Herb::AST::DocumentNode
  :children #=&amp;gt; Herb::AST::HTMLElementNode

    Herb::AST::HTMLElementNode
      :open_tag  #=&amp;gt; Herb::AST::HTMLOpenTagNode
      :tag_name
      :body      #=&amp;gt; [Herb::AST::HTMLTextNode, …]
      :close_tag #=&amp;gt; Herb::AST::HTMLCloseTagNode
      :is_void

        Herb::AST::HTMLOpenTagNode
          :tag_opening
          :tag_name
          :tag_closing
          :children #=&amp;gt; Herb::AST::HTMLAttributeNode
          :is_void

            Herb::AST::HTMLAttributeNode
              :name   #=&amp;gt; Herb::AST::HTMLAttributeNameNode
              :equals
              :value  #=&amp;gt; Herb::AST::HTMLAttributeValueNode

                Herb::AST::HTMLAttributeNameNode
                  :name

                Herb::AST::HTMLAttributeValueNode
                  :open_quote
                  :children #=&amp;gt; Herb::AST::LiteralNode
                  :close_quote
                  :quoted

                    Herb::AST::LiteralNode
                      :content

        Herb::AST::HTMLTextNode
          :content

        Herb::AST::HTMLCloseTagNode
          :tag_opening
          :tag_name
          :tag_closing
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;There are a few more observations to make. The &lt;code&gt;HTMLElementNode&lt;/code&gt; consists of three parts, the &lt;code&gt;#open_tag&lt;/code&gt; (&lt;code&gt;&amp;lt;span class=&quot;alert&quot;&amp;gt;&lt;/code&gt;, &lt;code&gt;#body&lt;/code&gt; (&lt;code&gt;Watch out!&lt;/code&gt;) and &lt;code&gt;#close_tag&lt;/code&gt; (&lt;code&gt;&amp;lt;/span&amp;gt;&lt;/code&gt;). It speaks of a &lt;code&gt;#body&lt;/code&gt; rather than &lt;code&gt;#children&lt;/code&gt; (in other node types) but these seem for all intents and purposes identical in function. The tag nodes (&lt;code&gt;HTMLOpenTagNode&lt;/code&gt; and &lt;code&gt;HTMLCloseTagNode&lt;/code&gt;) have &lt;code&gt;#tag_opening&lt;/code&gt; (&lt;code&gt;&amp;lt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;/&lt;/code&gt; respectively), &lt;code&gt;#tag_name&lt;/code&gt; (&lt;code&gt;span&lt;/code&gt;) and &lt;code&gt;#tag_closing&lt;/code&gt; (&lt;code&gt;&amp;gt;&lt;/code&gt;) methods. The &lt;code&gt;HTMLOpenTagNode&lt;/code&gt; has support for HTML attributes via &lt;code&gt;#children&lt;/code&gt; and supports void elements (i.e., self-closing tags like &lt;code&gt;&amp;lt;input /&amp;gt;&lt;/code&gt;) with &lt;code&gt;is_void&lt;/code&gt;. &lt;code&gt;HTMLAttributeNode&lt;/code&gt;s are themselves a compound of &lt;code&gt;#name&lt;/code&gt; and &lt;code&gt;#value&lt;/code&gt; where the &lt;code&gt;HTMLAttributeValueNode&lt;/code&gt; has itself a &lt;code&gt;#children&lt;/code&gt; collection of &lt;code&gt;Herb::AST::LiteralNode&lt;/code&gt;s.  The &lt;code&gt;LiteralNode&lt;/code&gt; and the &lt;code&gt;HTMLTextNode&lt;/code&gt; have &lt;code&gt;#content&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;—&lt;em&gt;1819, Sunday 20th April 2025.&lt;/em&gt;&lt;/p&gt;</content><author><name>Philip Poots</name></author><summary type="html">Herb, the new HTML-aware ERB parser library by Marco Roth, parses ERB templates into an abstract syntax tree. The tree has two main branches:</summary></entry><entry><title type="html">HTML Gardening with Herb</title><link href="https://www.crossingtheruby.com/2025/04/19/html-gardening-with-herb.html" rel="alternate" type="text/html" title="HTML Gardening with Herb" /><published>2025-04-19T00:00:00+00:00</published><updated>2025-04-19T00:00:00+00:00</updated><id>https://www.crossingtheruby.com/2025/04/19/html-gardening-with-herb</id><content type="html" xml:base="https://www.crossingtheruby.com/2025/04/19/html-gardening-with-herb.html">&lt;p&gt;&lt;a href=&quot;https://marcoroth.dev&quot;&gt;Marco Roth&lt;/a&gt; released a new HTML-aware ERB parser library called &lt;a href=&quot;https://herb-tools.dev&quot;&gt;Herb&lt;/a&gt; at the Ruby Kaigi conference a few days ago. Given I’ve been playing around with &lt;a href=&quot;https://www.phlex.fun&quot;&gt;Phlex&lt;/a&gt; over the last week and toying around with the idea of automating templating concerns in a conventional “views and partials” Rails application, it’s perfect timing.&lt;/p&gt;

&lt;p&gt;My preference for eschewing a templating language in favour of using the host language natively, inspired by Elm, led to explorations a few years ago which resulted in Michel Martens’ &lt;a href=&quot;https://github.com/soveran/hypertext&quot;&gt;Hypertext&lt;/a&gt; library. In the intervening years Joel Drapper has seen some traction with Phlex, conceived with the same ideals, but offering a much more robust and fully-featured solution which plays nicely with Rails.&lt;/p&gt;

&lt;p&gt;A key practical benefit of Phlex (widely misunderstood) is that it is a faithful abstraction of HTML (i.e., it doesn’t leak), offering a degree of correctness that a string based templating language like ERB can never provide. If the Ruby can’t interpret a Phlex “template” file (remember, it’s just Ruby) then there is a problem with your “HTML”. If Ruby interprets it, then it is valid HTML. In ERB, you can type anything you want, and you give yourself over to the grace and leniency of browser error recovery when it is presented with malformed HTML. &lt;a href=&quot;https://joel.drapper.me/leaky-strings/&quot;&gt;Strings are a leaky abstraction for HTML&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In one sense Herb closes this loop. Given it parses ERB files it is able to highlight malformed ERB. So adding it as a linter might give you a certain level of safety. I ran the parser on ClubCollect’s ERB files and in just under twelve-hundred files it found seventy-five parse errors. That’s a defect rate of six percent. Granted, it’s not the end of the world—they are probably all missing tags or malformed HTML, quirks that a browser will forgive—but I’d feel much more comfortable knowing that I couldn’t make those mistakes in the first place.&lt;/p&gt;

&lt;p&gt;Herb parses ERB files into an abstract syntax tree which models both the HTML and Ruby parts of an ERB template. The primary purpose of the library is to improve language and editor tooling when working with ERB. What I find more interesting is: firstly, being able to more accurately query the ERB than a simple text-based search; and secondly, being able to modify and/or transform template files programatically.&lt;/p&gt;

&lt;h2 id=&quot;querying-erb-semantically&quot;&gt;Querying ERB Semantically&lt;/h2&gt;

&lt;p&gt;ClubCollect’s codebase is over eleven years old which in modern terms is like ancient history. The accretion of different layers of symbolic sediment, templates subjected to the whimiscal vagaries of different styles and preferences, is something crying out for careful archeological excavation followed by judicious rehabilitation. To give a small, but practical example of stemming the ruinous tide of divergent directives, consider all of the ways that one might have declared a simple Bootstrap Alert component: plain HTML; interpolated Ruby in the HTML &lt;code&gt;class&lt;/code&gt; attribute; via a partial; via a helper method. In the first case, simply searching for the string &lt;code&gt;&quot;alert&quot;&lt;/code&gt; might yield what I’m looking for, but might also return a number of false positives.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;alert alert-primary&quot; role=&quot;alert&quot;&amp;gt;
  A simple primary alert—check it out!
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Bootstrap 4.5.6, this is archeology after all)&lt;/p&gt;

&lt;p&gt;With Herb, it is possible to parse the ERB, and because it models the HTML in an AST, it is possible to search for the value &lt;code&gt;&quot;alert&quot;&lt;/code&gt; present specifically inside an HTML &lt;code&gt;class&lt;/code&gt; attribute. This may not return all the cases, but it reduces the set of false positives.&lt;/p&gt;

&lt;h2 id=&quot;modifying-erb-programatically&quot;&gt;Modifying ERB Programatically&lt;/h2&gt;

&lt;p&gt;Let’s say that we want to coalesce our different compositions of Bootstrap Alert into some form of component. Maybe history hindered a former colleagues adherence to accessibility (maybe that former colleague was me!) and left out the &lt;code&gt;role&lt;/code&gt; attribute. Having located instances of the Bootstrap Alert it should be possible, with the Herb AST, to ensure conformance to the proper HTML definition, and if it falls short, supplement the existing definition with the missing piece(s).&lt;/p&gt;

&lt;p&gt;Looking further, but remaining in ERB, we could write a helper method and ensure that all instances of the Bootstrap Alert use the helper method, rewriting the AST, or simply re-rendering the ERB to file with this modification.&lt;/p&gt;

&lt;p&gt;And should one wish to make the switch to a more component-based view layer with GitHub’s ViewComponent or (should I win my colleagues over) Phlex, it should also be possible to automate the transition from ERB.&lt;/p&gt;

&lt;p&gt;These two Herb use-cases are things I would very much like to explore.&lt;/p&gt;

&lt;p&gt;—&lt;em&gt;1819, Saturday 19th April 2025.&lt;/em&gt;&lt;/p&gt;</content><author><name>Philip Poots</name></author><summary type="html">Marco Roth released a new HTML-aware ERB parser library called Herb at the Ruby Kaigi conference a few days ago. Given I’ve been playing around with Phlex over the last week and toying around with the idea of automating templating concerns in a conventional “views and partials” Rails application, it’s perfect timing.</summary></entry><entry><title type="html">[TIL] PostgreSQL Custom Sort Order</title><link href="https://www.crossingtheruby.com/2023/10/12/postgres-custom-sort-order.html" rel="alternate" type="text/html" title="[TIL] PostgreSQL Custom Sort Order" /><published>2023-10-12T00:00:00+00:00</published><updated>2023-10-12T00:00:00+00:00</updated><id>https://www.crossingtheruby.com/2023/10/12/postgres-custom-sort-order</id><content type="html" xml:base="https://www.crossingtheruby.com/2023/10/12/postgres-custom-sort-order.html">&lt;p&gt;Sometimes I want to compare a large enough portion of data from a spreadsheet against data in a PostgreSQL database. The spreadsheet contains a number of rows with an identifier but the records are in a random order and I want to preserve that ordering for the comparison. I therefore want to be able to order the result set from a PostgreSQL database query in a predetermined, custom sort order.&lt;/p&gt;

&lt;p&gt;Of course the straightforward workaround is to order the initial spreadsheet’s data by a column that is also natively sortable in the database and the need for a custom sort order vanishes, but trust me on this one, I want the original, random order.&lt;/p&gt;

&lt;p&gt;I have an array of identifiers, plucked from the spreadsheet, to use as my query criteria:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;membership_no,name
1325,Joe
1123,Ben
1548,Cat
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I can transform the &lt;code&gt;membership_no&lt;/code&gt; column into an array of values for populating the &lt;code&gt;WHERE&lt;/code&gt; clause:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SELECT
  membership_no,
  name
FROM
  members
WHERE
  membership_no IN ('1325','1123','1548');
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The results come back in as random an order as they went in. So how to order it according to the original order?&lt;/p&gt;

&lt;p&gt;The answer is to use the &lt;code&gt;array_position()&lt;/code&gt; function in an &lt;code&gt;ORDER BY&lt;/code&gt; clause, using the initial, randomly ordered array. It works as follows:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SELECT
  membership_no,
  name
FROM
  members
WHERE
  membership_no IN ('1325','1123','1548');
ORDER BY
  array_position(ARRAY['1325','1123','1548'], membership_no::text)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This returns the result set in the desired order:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; membership_no | name
--------------------------
 1325          | Joseph
 1123          | Benjamin
 1548          | Catriona
(3 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The first argument to &lt;code&gt;array_position&lt;/code&gt; is an array of values representing the desired order, and the second is the database column that will be used for sorting. The value of the column will be positioned according to the order of the values provided.&lt;/p&gt;

&lt;p&gt;If you look carefully, you’ll notice that the column value is typecast with &lt;code&gt;::text&lt;/code&gt;. This is necessary as the comparison requires the datatype of the column value and the values in the array to be the same. In this case I am providing the values as text, therefore the column value needs to be cast to a text value. Failure to do this will cause PostgreSQL to complain with an error similar to the following:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ERROR:  function array_position(text[], character varying) does not exist

HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Well, good thing I added an explicit type cast.&lt;/p&gt;

&lt;p&gt;—&lt;em&gt;2127, Thursday 12th October 2023.&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;references&quot;&gt;
  &lt;h3&gt;References&lt;/h3&gt;
  &lt;ul&gt;
    &lt;li&gt;
      &lt;a href=&quot;https://stackoverflow.com/questions/4088532/custom-order-by-explanation&quot;&gt;
        StackOverflow: Custom ORDER BY Explanation
      &lt;/a&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;a href=&quot;https://www.postgresql.org/docs/14/functions-array.html&quot;&gt;
        PostgreSQL Docs: &lt;code&gt;array_position()&lt;/code&gt;
      &lt;/a&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;a href=&quot;https://dba.stackexchange.com/questions/201837/order-by-gives-error-function-array-positiontext-character-varying-does-no&quot;&gt;
        Stack Exchange &amp;gt; Database Administrators &amp;gt; ORDER BY gives ERROR: function array_position(text[], character varying) does not exist
      &lt;/a&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/div&gt;</content><author><name>Philip Poots</name></author><summary type="html">Sometimes I want to compare a large enough portion of data from a spreadsheet against data in a PostgreSQL database. The spreadsheet contains a number of rows with an identifier but the records are in a random order and I want to preserve that ordering for the comparison. I therefore want to be able to order the result set from a PostgreSQL database query in a predetermined, custom sort order.</summary></entry><entry><title type="html">Setting and Clearing Intervals in the browser</title><link href="https://www.crossingtheruby.com/2022/06/20/setting-and-clearing-intervals.html" rel="alternate" type="text/html" title="Setting and Clearing Intervals in the browser" /><published>2022-06-20T00:00:00+00:00</published><updated>2022-06-20T00:00:00+00:00</updated><id>https://www.crossingtheruby.com/2022/06/20/setting-and-clearing-intervals</id><content type="html" xml:base="https://www.crossingtheruby.com/2022/06/20/setting-and-clearing-intervals.html">&lt;p&gt;When setting up two-way communication over websockets with Phoenix Channels recently I wanted to poll the server until a given event (on the server) had occurred, and then stop. I’ll understand if you start scratching your head and asking why on earth I’d want to poll when a websocket allows me to send notification of said event from the server directly. In this case the polling (via the websocket/channel) is a safety net for instances where the server-side event has happened before, or during, the page request meaning that the server-side event would have been broadcast to no-one. In other cases an exception might prevent the broadcast from being called.&lt;/p&gt;

&lt;p&gt;The options for doing this from the Web API off the top of my head were &lt;code&gt;setTimeout&lt;/code&gt; and &lt;code&gt;setInterval&lt;/code&gt;. The latter seemed the best option for this case, however there were two constraints which eventually expanded my knowledge.&lt;/p&gt;

&lt;p&gt;The first constraint was that I wanted to ensure the polling would stop once the event had been received. I was always working in the ignorance that a &lt;code&gt;setInterval&lt;/code&gt; would continue until the browser browsed to another page or the tab/window was closed. It turns out that you can store a reference to the interval by assigning the result of the call to a variable:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;var intervalId = setInterval(messageServerFn, 5000)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And I learned that that interval reference comes in handy when you want to stop it. There is a related function in the Web API named &lt;code&gt;clearInterval&lt;/code&gt;. This function takes an interval reference as a parameter:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;clearInterval(intervalId)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And voilà, the function passed to &lt;code&gt;setInterval&lt;/code&gt; will no longer be called. Neat.&lt;/p&gt;

&lt;p&gt;The trouble with &lt;code&gt;setInterval&lt;/code&gt; is that it first runs only &lt;em&gt;after&lt;/em&gt; the interval length. In our case above, with a value of five thousand milliseconds, the function will be called for the first time only after five seconds. I wanted the function to be called immediately. This was my second constraint.&lt;/p&gt;

&lt;p&gt;It turns out there a few ways of doing this. The most straightforward was not immediately obvious to me given it was so simple, but just calling the function independently of the call to &lt;code&gt;setInterval&lt;/code&gt; does the trick.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;messageServerFn()
var intervalId = setInterval(messageServerFn, 5000)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;No point in making it more complicated than it is.&lt;/p&gt;

&lt;p&gt;—&lt;em&gt;Monday, 20th June 2022.&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;references&quot;&gt;
  &lt;h3&gt;References&lt;/h3&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/setInterval&quot;&gt;&lt;code&gt;setInterval&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/clearInterval&quot;&gt;&lt;code&gt;clearInterval&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/10563344/how-to-start-setinterval-loop-immediately&quot;&gt;How to start the setInterval loop immediately?&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/6685396/execute-the-setinterval-function-without-delay-the-first-time&quot;&gt;Execute the setInterval function without delay the first time&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/div&gt;</content><author><name>Philip Poots</name></author><summary type="html">When setting up two-way communication over websockets with Phoenix Channels recently I wanted to poll the server until a given event (on the server) had occurred, and then stop. I’ll understand if you start scratching your head and asking why on earth I’d want to poll when a websocket allows me to send notification of said event from the server directly. In this case the polling (via the websocket/channel) is a safety net for instances where the server-side event has happened before, or during, the page request meaning that the server-side event would have been broadcast to no-one. In other cases an exception might prevent the broadcast from being called.</summary></entry><entry><title type="html">Massaging git diff</title><link href="https://www.crossingtheruby.com/2022/06/19/massaging-git-diff.html" rel="alternate" type="text/html" title="Massaging git diff" /><published>2022-06-19T00:00:00+00:00</published><updated>2022-06-19T00:00:00+00:00</updated><id>https://www.crossingtheruby.com/2022/06/19/massaging-git-diff</id><content type="html" xml:base="https://www.crossingtheruby.com/2022/06/19/massaging-git-diff.html">&lt;p&gt;Recently I’ve been working in an Elixir Phoenix application. It’s a legacy application (first commit: December 2015) which has seen it move through both Elixir and Phoenix versions since Elixir 1.1.0 and Phoenix 1.0.4 all the way to Elixir 1.13.4 and Phoenix 1.6.10.&lt;/p&gt;

&lt;p&gt;One of the features that was introduced around the half-way point was &lt;code&gt;mix format&lt;/code&gt;. This Mix task makes use of a code formatter to automate code style into a consistent format. Runnig this in one go was seen as a violation of the Git history and so much of the formatting has been happening incrementally since that point onwards. There is now a &lt;code&gt;mix format --check-formatted&lt;/code&gt; task in CI which doesn’t reformat the code, but reports on only on violations. This means there is a divergence between a freshly minted file that is formatted with &lt;code&gt;mix format&lt;/code&gt; and a file that has been manually massaged into a format that doesn’t report any violations. (This is arguably a failing of the formatter in dictating one unambigous style—the conversation has just been moved from one bike-shed to another, rather than shedding it altogether.)&lt;/p&gt;

&lt;p&gt;This potential mismatch, paired with the desire to constrain commits and pull requests to only the changes related to the goal of the pull request itself, means that I only want to run &lt;code&gt;mix format&lt;/code&gt; on the files that I have changed. And just to be difficult, technically only on the changes that I have made, rather than the whole file. I also want to see the diff between my changes and my changes formatted by &lt;code&gt;mix format&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I was looking for an easy way to do this on the command line (bash). The first challenge was returning only the file names of files I had changed. And to ensure a diff between the &lt;code&gt;mix format&lt;/code&gt; result and my edits, my edits are added to the Git staging area. The status therefore looks something like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ git status
On branch demonstrate-diff
Your branch is up to date with 'origin/demonstrate-diff'.

Changes to be committed:
  (use &quot;git restore --staged &amp;lt;file&amp;gt;...&quot; to unstage)
	modified:    lib/demo/helpers.ex
        modified:    lib/demo/schemas/book.ex
        deleted:     lib/demo/schemas/codex.ex

Changes not staged for commit:
  (use &quot;git add &amp;lt;file&amp;gt;...&quot; to update what will be committed)
  (use &quot;git restore &amp;lt;file&amp;gt;...&quot; to discard changes in working directory)
	modified:   lib/demo/workers/export.ex
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The idea is to pick out the necessary file names and then apply &lt;code&gt;mix format&lt;/code&gt; to each file. We can use the &lt;code&gt;--name-only&lt;/code&gt; option on &lt;code&gt;git diff&lt;/code&gt; to return the names:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ git diff --name-only
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This however returns all the names:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;lib/demo/helpers.ex
lib/demo/schemas/book.ex
lib/demo/schemas/codex.ex
lib/demo/workers/export.ex
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and we only want those from the staging area, the first three. To do that we can use the option &lt;code&gt;--cached&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ git diff --name-only --cached
lib/demo/helpers.ex
lib/demo/schemas/book.ex
lib/demo/schemas/codex.ex
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The problem with returning everything from the staging area is that it will include all operations, including deleted files, which won’t succeed if passed to &lt;code&gt;mix format&lt;/code&gt;. We need to turn to the &lt;code&gt;--diff-filter&lt;/code&gt; option. I’m really only looking for new files that have added (&lt;code&gt;A&lt;/code&gt;) or files that have been modified (&lt;code&gt;M&lt;/code&gt;):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ git diff --name-only --cached --diff-filter=AM
lib/demo/helpers.ex
lib/demo/schemas/book.ex
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now that we have the file names we need to pass those to &lt;code&gt;mix format&lt;/code&gt;. This would normally be done with &lt;code&gt;xargs&lt;/code&gt; or with a &lt;code&gt;bash&lt;/code&gt; for-loop, but I have recently been enjoying the simplicity of &lt;a href=&quot;https://github.com/soveran/map&quot;&gt;&lt;code&gt;map&lt;/code&gt;&lt;/a&gt;. Using &lt;code&gt;map&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ git diff --name-only --cached --diff-filter=AM | map f 'mix format &quot;$f&quot;'
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And there we have it. &lt;code&gt;mix format&lt;/code&gt; being only run for files that have been added or modified. Now I can compare the changes &lt;code&gt;mix format&lt;/code&gt; has made with &lt;code&gt;git diff&lt;/code&gt; and then selectively add those that are relevant to my own edits, ignoring those that aren’t, using &lt;code&gt;git add --interactive --patch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;—&lt;em&gt;Sunday, 19th June 2022.&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;references&quot;&gt;
  &lt;h3&gt;References&lt;/h3&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/33610682/git-list-of-staged-files&quot;&gt;Git list of staged files&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://yanneves.medium.com/how-to-git-diff-only-for-modified-files-c5bf43b8721e&quot;&gt;How to git diff only for modified files&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://github.com/soveran/map&quot;&gt;&lt;code&gt;map&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/div&gt;</content><author><name>Philip Poots</name></author><summary type="html">Recently I’ve been working in an Elixir Phoenix application. It’s a legacy application (first commit: December 2015) which has seen it move through both Elixir and Phoenix versions since Elixir 1.1.0 and Phoenix 1.0.4 all the way to Elixir 1.13.4 and Phoenix 1.6.10.</summary></entry><entry><title type="html">Basic Form: Field Presentation</title><link href="https://www.crossingtheruby.com/2021/07/04/basic-form-field-presentation.html" rel="alternate" type="text/html" title="Basic Form: Field Presentation" /><published>2021-07-04T00:00:00+00:00</published><updated>2021-07-04T00:00:00+00:00</updated><id>https://www.crossingtheruby.com/2021/07/04/basic-form-field-presentation</id><content type="html" xml:base="https://www.crossingtheruby.com/2021/07/04/basic-form-field-presentation.html">&lt;p&gt;After &lt;a href=&quot;https://www.crossingtheruby.com/2021/07/03/basic-form-field-decoration.html&quot;&gt;decorating our &lt;code&gt;Field&lt;/code&gt;&lt;/a&gt; class we now have everything we need to render our field to HTML.&lt;/p&gt;

&lt;p&gt;We can start for simplicity’s sake with one method, and if necessary split it out into smaller pieces.&lt;/p&gt;

&lt;p&gt;We have any number of method names to choose: &lt;code&gt;to_s&lt;/code&gt;, &lt;code&gt;to_html&lt;/code&gt;, and &lt;code&gt;render&lt;/code&gt;. I’m going to go with the first as a Ruby convention.&lt;/p&gt;

&lt;p&gt;In terms of building up our HTML I’ll use simple string interpolation for now.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class Field
  …
  def to_s
    %Q(
      &amp;lt;div class=&quot;form-group&quot;&amp;gt;
        &amp;lt;label for=&quot;#{input_id}&quot;&amp;gt;
          #{label}
        &amp;lt;/label&amp;gt;
        &amp;lt;input 
            type=&quot;#{type}&quot; 
            name=&quot;#{input_name}&quot;
            value=&quot;#{value}&quot;
            class=&quot;form-control&quot; 
            id=&quot;#{input_id}&quot; 
            aria-describedby=&quot;#{help_id}&quot;&amp;gt;
        &amp;lt;small 
            id=&quot;#{help_id}&quot; 
            class=&quot;form-text text-muted&quot;&amp;gt;
          #{help}
        &amp;lt;/small&amp;gt;
      &amp;lt;/div&amp;gt;
    )
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The only thing that hasn’t been included is the error HTML. That’s a little trickier when doing it this route.&lt;/p&gt;

&lt;p&gt;Let’s add a couple of methods to round out this example with the knowledge in the back of our head that a more gracious templating approach would invalidate the need for these methods.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class Field
  …
  def error_class
    errors.any? ? &quot;is-invalid&quot; : &quot;&quot;
  end
  
  def error_text
    errors.join(&quot;, &quot;)
  end
  
  def error_html
    %Q(
      &amp;lt;div class=&quot;invalid-feedback&quot;&amp;gt;
        #{error_text}
      &amp;lt;/div&amp;gt;
    )
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;These additions allow us to modify the &lt;code&gt;to_s&lt;/code&gt; method as follows:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;  class Field
    …
    def to_s
      %Q(
        &amp;lt;div class=&quot;form-group&quot;&amp;gt;
          &amp;lt;label for=&quot;#{input_id}&quot;&amp;gt;
            #{label}
          &amp;lt;/label&amp;gt;
          &amp;lt;input 
              type=&quot;#{type}&quot; 
              name=&quot;#{input_name}&quot;
              value=&quot;#{value}&quot;
-             class=&quot;form-control&quot;
+             class=&quot;form-control #{error_class}&quot;
              id=&quot;#{input_id}&quot; 
              aria-describedby=&quot;#{help_id}&quot;&amp;gt;
+         #{error_html}
          &amp;lt;small 
              id=&quot;#{help_id}&quot; 
              class=&quot;form-text text-muted&quot;&amp;gt;
            #{help}
          &amp;lt;/small&amp;gt;
        &amp;lt;/div&amp;gt;
      )
    end
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And that completes the field experiment in our basic form.&lt;/p&gt;

&lt;p&gt;The process of pulling everything apart and putting it back together again has been helpful in concretely visualising the more abstract distinctions we had made in the concepts with code.&lt;/p&gt;

&lt;p&gt;It also makes very clear just how many subtle layers there are in arriving at the final HTML, and also how much the form/model object and the choice of templating approach influence the final solution.&lt;/p&gt;

&lt;p&gt;—&lt;em&gt;Sunday 4th July 2021.&lt;/em&gt;&lt;/p&gt;</content><author><name>Philip Poots</name></author><summary type="html">After decorating our Field class we now have everything we need to render our field to HTML.</summary></entry><entry><title type="html">Basic Form: Field Decoration</title><link href="https://www.crossingtheruby.com/2021/07/03/basic-form-field-decoration.html" rel="alternate" type="text/html" title="Basic Form: Field Decoration" /><published>2021-07-03T00:00:00+00:00</published><updated>2021-07-03T00:00:00+00:00</updated><id>https://www.crossingtheruby.com/2021/07/03/basic-form-field-decoration</id><content type="html" xml:base="https://www.crossingtheruby.com/2021/07/03/basic-form-field-decoration.html">&lt;p&gt;&lt;a href=&quot;https://www.crossingtheruby.com/2021/07/02/basic-form-field.html&quot;&gt;Yesterday&lt;/a&gt; we created a &lt;code&gt;Field&lt;/code&gt; class that contained all of the source data necessary for building out a field in HTML.&lt;/p&gt;

&lt;p&gt;Today we can look at how to decorate this data with some helper methods to calculate the necessary derivatives.&lt;/p&gt;

&lt;p&gt;Our categorised and sorted &lt;a href=&quot;https://www.crossingtheruby.com/2021/06/22/basic-form-variable-categories.html&quot;&gt;list of data&lt;/a&gt; defines the following derived elements coexisting alongside the original data:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;input[id]&lt;/code&gt; and &lt;code&gt;label[for]&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;input[name]&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;help[id]&lt;/code&gt; and &lt;code&gt;input[aria-describedby]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s add some methods to our field class:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class Field
  …
  def input_id
    [object_name, name].join(&quot;_&quot;)
  end
  
  def input_name
    &quot;#{object_name}[#{name}]&quot;
  end
  
  def help_id
    [input_id, &quot;help&quot;].join(&quot;_&quot;)
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is all we need. We could memoize the &lt;code&gt;input_id&lt;/code&gt; to ensure it isn’t being calculated from scratch each time. This way we ensure that it remains identical (an edge-case but it feels safer given the values need to be identical for the label and input to correspond properly in the HTML).&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class Field
  …
  def input_id
    @input_id ||= [object_name, name].join(&quot;_&quot;)
  end
  …
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I believe this is everything we would need for our basic field before rendering it to HTML.&lt;/p&gt;

&lt;p&gt;—&lt;em&gt;Saturday 3rd July 2021.&lt;/em&gt;&lt;/p&gt;</content><author><name>Philip Poots</name></author><summary type="html">Yesterday we created a Field class that contained all of the source data necessary for building out a field in HTML.</summary></entry><entry><title type="html">Basic Form: Field</title><link href="https://www.crossingtheruby.com/2021/07/02/basic-form-field.html" rel="alternate" type="text/html" title="Basic Form: Field" /><published>2021-07-02T00:00:00+00:00</published><updated>2021-07-02T00:00:00+00:00</updated><id>https://www.crossingtheruby.com/2021/07/02/basic-form-field</id><content type="html" xml:base="https://www.crossingtheruby.com/2021/07/02/basic-form-field.html">&lt;p&gt;Although I started with a rough design for a &lt;a href=&quot;https://www.crossingtheruby.com/2021/07/01/basic-form-widget.html&quot;&gt;widget&lt;/a&gt; let’s define a field to act as a foundation. We listed and categorised the necessary attributes in &lt;a href=&quot;https://www.crossingtheruby.com/2021/06/23/basic-form-variable-hierarchy.html&quot;&gt;variable categories&lt;/a&gt; and the relevant list is the last one that categorises by data source.&lt;/p&gt;

&lt;p&gt;That means we need the object name, the attribute name, the attribute value and type, and all of the user-facing text comprising of label, help and errors.&lt;/p&gt;

&lt;p&gt;There is a choice here to also pass in a more global form object like Rails does by default, or only pass in the necessary information that the field needs. We’ll go with the latter for now.&lt;/p&gt;

&lt;p&gt;A rough sketch:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class Field
  ATTRS = %i(
    errors
    help
    label
    name
    object_name
    type
    value
  )
  attr_accessor *ATTRS
  
  def initialize(attrs)
    ATTRS.each do |attr|
      instance_variable_set(&quot;@#{attr}&quot;, attrs.dig(attr))
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This would be called in the following way, assuming our &lt;a href=&quot;https://www.crossingtheruby.com/2021/06/17/basic-form-rails.html&quot;&gt;ActiveModel EmailSubscription object&lt;/a&gt; instantiated as &lt;code&gt;@form&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Field.new({
  errors: @form.errors[:email],
  help: &quot;We'll never share your email with anyone else.&quot;,
  label: &quot;Email address&quot;,
  name: :email,
  object_name: :subscription,
  type: :email,
  value: @form.email
})
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We could of course later figure out a way to make the passing of these details a little less repetitive.&lt;/p&gt;

&lt;p&gt;Given this field object we can extend the class with a few methods to generate the necessary elements before rendering it.&lt;/p&gt;

&lt;p&gt;—&lt;em&gt;Friday 2nd July 2021.&lt;/em&gt;&lt;/p&gt;</content><author><name>Philip Poots</name></author><summary type="html">Although I started with a rough design for a widget let’s define a field to act as a foundation. We listed and categorised the necessary attributes in variable categories and the relevant list is the last one that categorises by data source.</summary></entry><entry><title type="html">Basic Form: Widget</title><link href="https://www.crossingtheruby.com/2021/07/01/basic-form-widget.html" rel="alternate" type="text/html" title="Basic Form: Widget" /><published>2021-07-01T00:00:00+00:00</published><updated>2021-07-01T00:00:00+00:00</updated><id>https://www.crossingtheruby.com/2021/07/01/basic-form-widget</id><content type="html" xml:base="https://www.crossingtheruby.com/2021/07/01/basic-form-widget.html">&lt;p&gt;I’m going to front up and admit that I got lost among the Bureaucrat widgets. The key challenge is that a widget does not have the field object exposed to it, but only a small subset of the field attributes. A field has a widget, but a widget does not have access to a field.&lt;/p&gt;

&lt;p&gt;To be as comprehensive as I would like to be, and to broaden the responsibility of a widget to the whole field HTML, including a label, errors, and a hint in addition to the form control then a widget would need access to the field object itself.&lt;/p&gt;

&lt;p&gt;My imagined API goes something like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;field = Field.new
widget = Widget.new(field)
widget.render
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To give control back to the field itself and make it responsible for rendering itself then you could do the following:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class Field
  def to_s
    Widget.new(field).render
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is obviously a little simplistic—there are no avenues for configuration, nor means for deciding which widget should be used—but let’s run with this for now.&lt;/p&gt;

&lt;p&gt;A widget is then responsible for querying the field to get the necessary information:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class Widget
  attr_accessor :field
  
  def initialize(field)
    @field = field
  end
  
  def render
    # HTML
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The class could be filled out with methods that query the field object to generate the appropriate matching &lt;code&gt;id&lt;/code&gt;/&lt;code&gt;for&lt;/code&gt; attribute pairs, the &lt;code&gt;name&lt;/code&gt; and so on. These methods could then be called from the &lt;code&gt;render&lt;/code&gt; method to put everything in place in the HTML.&lt;/p&gt;

&lt;p&gt;I think that’s how I would design it. Whether you then need a widget to separate this from the field is another matter (versus keeping it all in the &lt;code&gt;Field&lt;/code&gt; class) but the principle is the same.&lt;/p&gt;

&lt;p&gt;—&lt;em&gt;Thursday 1st July 2021.&lt;/em&gt;&lt;/p&gt;</content><author><name>Philip Poots</name></author><summary type="html">I’m going to front up and admit that I got lost among the Bureaucrat widgets. The key challenge is that a widget does not have the field object exposed to it, but only a small subset of the field attributes. A field has a widget, but a widget does not have access to a field.</summary></entry><entry><title type="html">Basic Form: Bureaucrat (3)</title><link href="https://www.crossingtheruby.com/2021/06/30/basic-form-bureaucrat-3.html" rel="alternate" type="text/html" title="Basic Form: Bureaucrat (3)" /><published>2021-06-30T00:00:00+00:00</published><updated>2021-06-30T00:00:00+00:00</updated><id>https://www.crossingtheruby.com/2021/06/30/basic-form-bureaucrat-3</id><content type="html" xml:base="https://www.crossingtheruby.com/2021/06/30/basic-form-bureaucrat-3.html">&lt;p&gt;The widget associated with a Bureaucrat field is limited to the form control itself. The label, help text and errors are not included in a widget out of the box.&lt;/p&gt;

&lt;p&gt;Reviewing the field class revealed a &lt;code&gt;label_tag&lt;/code&gt; method which produces a label tag with the correct value for the &lt;code&gt;for&lt;/code&gt; attribute, associating it with the &lt;code&gt;input&lt;/code&gt;’s &lt;code&gt;id&lt;/code&gt; element.&lt;/p&gt;

&lt;p&gt;I must admit I didn’t quite understand why presentation concerns like this were mixed up with the field class while the widget, which I assumed would deal with all of the presentation logic, only produced the form control itself.&lt;/p&gt;

&lt;p&gt;In a template the following code would be necessary to display all of the different facets of a field:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;form-group&quot;&amp;gt;
  &amp;lt;%= field.label_tag %&amp;gt;
  &amp;lt;%= field %&amp;gt;
  &amp;lt;% unless field.errors.empty? %&amp;gt;
    &amp;lt;div class=&quot;invalid-feedback&quot;&amp;gt;
      &amp;lt;%= field.errors %&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;% end %&amp;gt;
  &amp;lt;% unless field.help_text.empty? %&amp;gt;
    &amp;lt;small class=&quot;form-text text-muted&quot;&amp;gt;
      &amp;lt;%= field.help_text %&amp;gt;
    &amp;lt;/small&amp;gt;
  &amp;lt;% end %&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I feel like this defeats the purpose of abstracting away the form control into a widget. Either the widget should deal with everything, or there should be no widget.&lt;/p&gt;

&lt;p&gt;To compare it with Rails, the widget is the raw &lt;code&gt;input_tag&lt;/code&gt; helper, whereas my sensibilities would want a widget to be more like an all-inclusive Formtastic or Simple Form helper.&lt;/p&gt;

&lt;p&gt;So while Bureaucrat starts down a declarative path, for me it doesn’t go far enough. The inclusion of a &lt;code&gt;label_tag&lt;/code&gt; in the field, but nothing for errors or help text is a little short-sighted. The argument might be that you can easily extend Bureaucrat to add them, and that’s a fair point. But to arrive at a solution that caters to all of the elements of a form control then also needs the introduction of a custom widget. Perhaps we can take advantage of the inheritance hierarchy to make this a reality.&lt;/p&gt;

&lt;p&gt;—&lt;em&gt;Wednesday 30th June 2021.&lt;/em&gt;&lt;/p&gt;</content><author><name>Philip Poots</name></author><summary type="html">The widget associated with a Bureaucrat field is limited to the form control itself. The label, help text and errors are not included in a widget out of the box.</summary></entry></feed>