Today I Learned Notes to self about software development

    Hotwire - Turbo Drive Intro

    Turbo Drive accelerates links and form submissions by negating the need for full page reloads.

    What does this mean?

    In vague terms, Turbo Drive:

    • monitors all <a href>s and form submissions to the host domain
    • when detected, it prevents the Browser from performing it’s usual action (following href or form action) and instead:
      • performs the action in the background with JavaScript
        • updating the browsers current URL with the History API
        • requesting the next page using the Fetch API
          • form submissions will follow the redirect
        • rendering the response
      • when rendering, Turbo Drive:
        • replaces the contents of <body> and merge the contents of <head>
        • this preserves the JS window, DOM objects, <html> across page navigations

    Page navigation is a visit to a location (URL) with an action (one of the following: advance, replace, or restore).

    A visit contains everything that happens from the “click” to the render of the response. Things like:

    • changing browser history
    • issuing network request
    • restoring a copy of the page from cache
    • rendering the final response
    • updating the scroll position

    The point of merging instead of replacing the <head> elements is that if <title> or <meta> tags change, say, they will be updated as expected, but if links to assets are the same, they won’t be touched and therefore the browser won’t process them again

    Application Visits

    • have an action of advance or replace
      • this determines how the browser history is updated
      • advance is the default action and pushes a new entry onto the browser history stack.
        • in native apps, the advance action looks like a new Activity view popping onto the previous view
      • replace does what it sounds like, it replaces the current history entry with the next location. that history location is discarded. need the data-turbo-action="replace" attribute or Turbo.visit("/edit", { action: "replace" }).
    • initiated by clicking a Turbo Drive link or by executing Turbo.visit(url)
    • always performs a network request and renders the response

    If possible, Turbo Drive will render a preview of the page from cache immediately after the visit starts. This improves the perceived speed of frequent navigation between the same pages.

    • If the location of the visit has an anchor, Turbo Drive will attempt to scroll to it

    Restoration Visits

    This type of visit is automatically performed when you navigate with the “Back” or “Forward” buttons in a Browser or the equivalent iOS and Android apps.

    If possible, Turbo Drive will render a copy of the page from cache without making a request. Otherwise, it will retrieve a fresh copy of the page over the network.

    The scroll position of each page is saved before navigating away from a page and is restored during a restoration visit.

    Do not attempt to manually make a restoration visit.

    Performing a visit with a different method

    Links by default submit GET requests to the server. You can change that with the data-turbo-method attribute:

    <a href="/articles/54" data-turbo-method="delete">Delete the article</a>
    

    You should consider that for accessibility reasons, it’s better to use actual forms and buttons for anything that’s not a GET. — Performing a visit with a different method

    TODO: I want a source to backup this claim. I know that GET requests are “safe” and should not modify a resource so maybe that also implies that anchor elements should never modify resources? If so, then does that mean I shouldn’t use anchor elements for delete requests? That would mean I need to use a form to make delete requests and I don’t think I’ve seen a form setup to perform a delete request in the wild before. UPDATE Here’s one short mention

    • https://railsdesigner.com/link-button-to/ This mentions that link_to method: :post actually creates a hidden form when clicked
    • https://mixandgo.com/learn/ruby-on-rails/link-to This mentions that link_to data: { turbo_method: :delete } creates a hidden form as well
    • https://hotwire.io/documentation/turbo/handbook/drive The source code for link_to always creates an anchor element, so is the “form” something that happens client-side? Did the behavior change? or are the previous two articles wrong?
    • https://apidock.com/rails/ActionView/Helpers/UrlHelper/link_to/ Aha!

      When you do a link_to “Delete”, @some_obj, :method => “delete”, :confirm => “Are you sure?” Rails 3 will generate Delete rails.js will creates observers that convert that into a form. Be aware that this probably won’t work as a link from inside a form (since forms in forms isn’t valid).

    • https://apidock.com/rails/ActionView/Helpers/UrlHelper/link_to#1077–method-delete-etc- so this is probably still true and it happens client-side. I assume the reason why articles still say to use actual forms is because it’s more semantic, doesn’t rely as heavily on JS, and is better for SEO and accessibility.

    Confirmation

    You can make a link/button require confirmation before performing the visit.

    Add:

    • data-turbo-method
    • data-turbo-confirm
    <a href="/articles" data-turbo-method="get" data-turbo-confirm="Do you want to leave this page?">Back to articles</a>
    <a href="/articles/54" data-turbo-method="delete" data-turbo-confirm="Are you sure you want to delete the article?">Delete the article</a>
    

    The default behavior is to execute the browser’s confirm function, but you can customize the behavior with Turbo.config.forms.confirm = confirmMethod.

    <a href="/" data-turbo="false">Disabled</a>
    
    <form action="/messages" method="post" data-turbo="false">
      <!-- … -->
    </form>
    

    This attribute transfers to children elements.

    <div data-turbo="false">
      <a href="/">Disabled</a>
      <form action="/messages" method="post">
        <!-- … -->
      </form>
    </div>
    

    both the anchor and form have turbo disabled.

    <div data-turbo="false">
      <a href="/" data-turbo="true">Enabled</a>
    </div>
    

    Inversly, if Turbo Drive is globally off

    import { Turbo } from "@hotwired/turbo-rails"
    Turbo.session.drive = false
    

    you can turn it on for specific links like this

    <a href="/" data-turbo="true" data-turbo-stream="true">Enabled</a>
    

    Form Submissions

    Turbo Drive handles form submissions in a manner similar to link clicks. The key difference is that form submissions can issue stateful requests using the HTTP POST method, while link clicks only ever issue stateless HTTP GET requests. — Form Submissions

    TODO This is confusing because the article has several examples of anchor elements with data-turbo-method="delete". Do they mean outside of Turbo Drive, form submissions use POST and links always use GET? That seems irrelavent to mention when comparing how things are handled with Turbo Drive. It’ would be clearer to say that “because forms submissions are stateful, Turbo Drive dispatches events at different points of the submission.

    Events:

    1. turbo:submit-start
    2. turbo:before-fetch-request
    3. turbo:before-fetch-response
    4. turbo:submit-end

    These events are emitted from the <form> and bubble up through the document.

    You can use these events to customize the appearance of the form when a submission occurs.

    Redirecting

    Turbo Drive expects a 303 redirect response code and will follow and use to update the page without a full reload.

    Turbo Drive doesn’t render from 2xx response codes after a POST is because browser’s have built in behavior that Turbo can’t replicate or improve.

    When the response code is 4xx or 5xx, validation error messages are displayed.

    If the form submission is a GET request, you may render the directly rendered response by giving the form a data-turbo-frame target. If you’d like the URL to update as part of the rendering also pass a data-turbo-action attribute.

    Streaming

    Servers may also respond to form submissions with a Turbo Streams message by sending the header Content-Type: text/vnd.turbo-stream.html followed by one or more <turbo-stream> elements in the response body. This lets you update multiple parts of the page without navigating

    • https://stackoverflow.com/a/56505707
    • https://usability.yale.edu/web-accessibility/articles/links
    • https://www.a11yproject.com/posts/creating-valid-and-accessible-links/#when-should-you-use-a-button-instead%3F
    • https://adrianroselli.com/2016/01/links-buttons-submits-and-divs-oh-hell.html

    rails routes command flags

    TIL about helpful flags built-in to the rails routes task.

    -g

    allows you to grep the output and filter for any routes that partially match the URL helper method name, the HTTP verb, or the URL path.


    Example: rails routes -g new_comment rails routes -g POST rails routes -g admin
    -c

    allows you to filter output by controller name.


    Example: rails routes -c users rails routes -c admin/users rails routes -c Posts rails routes -c Devise::Sessions
    --expanded

    makes the output easier to view in smaller terminals.


    Example: rails routes --expanded
    --unused

    lists routes that are "unused" in your application. An "unused" route in Rails is a route that is defined in the config/routes.rb file but is not referenced by any controller action or view in your application


    Example: rails routes --unused

    Rails guide source

    WSL Woes

    Whenever you copy/paste files from Windows to the Linux subsystem I always get these weird Zone Identifier files that I don’t want!

    You can remove them all with this command:

    find . -name "*:Zone.Identifier" -type f -delete
    

    Configuring Solid Cable on Render

    Rails 8 added a lot of new features. Solid Cable comes as a drop in replacement to Action Cable and, by default, it uses the database instead of requiring Redis.

    I generated a new app (with Postgres), added some tables and turbo streams and then deployed to Render. The deployment failed!

    Exited with status 1 while building your code.

    ArgumentError: No unique index found for id (ArgumentError)
          raise ArgumentError, "No unique index found for #{name_or_columns}"

    This error message wasn’t very clear! But the line of code pointed to a broadcasts_to in my Comment model, so I was pretty sure it was Solid Cable related.

    after_create_commit -> { broadcast_before_to "...", target: "...", partial: "..." }
    

    I learned that Solid Cable runs in a separate database, which I assume Render didn’t like. I followed the instructions in the Solid Cable README to configure Solid Cable to work with a single database and I was able to deploy to Render!

    1. Copy the contents of db/cable_schema.rb into a normal migration and delete db/cable_schema.rb
    2. Remove connects_to from config/cable.yml
    3. bin/rails db:migrate

    🎉

    Markdown footnotes

    I forget this all the time.

    just do this:

    Some text[^1].
    
    Some other text[^2].
    
    The identifier in the square brackets does not have to be numeric[^my_footnote].
    
    [^1]: Some footnote.
    [^2]: Other footnote.
    
    [^my_footnote]: This also works fine.
    

    This is not supported in GitHub repository code.