Today I Learned Notes to self about software development

    Beware— Default Headless Browser Size

    Ran into a hard-to-debug error where a nav link was unable to be clicked on because it wasn’t “visible”. Printing the HTML of the page and turning off headless proved to me that the link was both present and visible, so I was confused for a while.

    Apparently, the default browser size for headless chrome is Something like 800, 600 which almost certainly will activate a responsive layout that could cause some elements (like navbar links) to become “invisible”.

    The browser has a wider default size in non-headless mode, so it was not immediately obvious to me that an element could be invisible when the browser is headless.

    This makes me think that it should be best practice to always specify a default browser size for tests 🤔

    Active Record methods for associations

    I learned about a few useful methods that I hadn’t heard of before.

    reflect_on_association

    This method allows you to check if an association exists, without making a database query.

    • Will return nil if no associated records are found.
    • Will return an AggregateReflection object if records are found.
    class Post < ActiveRecord::Base
      has_many :upvotes
    end
    class Upvote < ActiveRecord::Base
      belongs_to :post
    end
    
    Post.reflect_on_association(:upvotes)
    

    extract_associated

    Useful if you need to get all associated objects from an ActiveRecord::Relation.

    Assuming:

    class Author < ActiveRecord::Base
      has_many :books
    end
    
    class Book < ActiveRecord::Base
      belongs_to :author
    end
    

    and we want to get all the books from a list of authors…

    Normally, my first thought we be to do something like this:

    Author.all.includes(:books).map(&:books) 
    

    but there is a more readable way to do this with extract_associated.

    Author.all.extract_associated(:books)
    

    which actually does something similar, so the benefit is mostly the readability.

    missing

    Ever need to find records with no associated records?

    For example:

    class City < ActiveRecord::Base
      has_many :photos
    end
    
    class Photo < ActiveRecord::Base
      belongs_to :city
    end
    

    if I want to find all cities that have no photos, previously, I thought you needed to do something like this:

    City.includes(:photos).where(photos: { city_id: nil })
    

    missing does this.

    City.where.missing(:photos)
    

    associated

    New in Rails 7. Sort of the inverse of missing, but associated checks for the presence of an association.

    Finding all the cities that have photos is as simple as:

    City.where.associated(:photos)
    

    When should you use CSS floats?

    I know people say never to use floats anymore, but surely they can still be used for whatever they were originally intended for… right?

    The float CSS property places an element on the left or right side of its container, allowing text and inline elements to wrap around it. The element is removed from the normal flow of the page, though still remaining a part of the flow (in contrast to absolute positioning). — MDN Docs

    Seems unique enough. I don’t think flexbox or CSS grid (the modern layout techniques) do a better job at this.

    The float property was introduced to allow web developers to implement layouts involving an image floating inside a column of text, with the text wrapping around the left or right of it. The kind of thing you might get in a newspaper layout.

    So the original purpose was to position images within text in a few specific ways.

    css-float-docs-demo.gif

    <div>
      <div>Float me</div>
      As much mud in the streets as if the waters had but newly retired from the face of the earth, and it would not be wonderful to meet a Megalosaurus, forty feet long or so, waddling like an elephantine lizard up Holborn Hill.
    </div>
    

    More depth on floats here.

    URL helpers with fragment

    When using a URL helper you can specify a fragment with the anchor option:

    post_path(post.root, anchor: "post_#{post.id}")
    # => "/posts/42#post_69
    

    Previously, I was manually doing something similar like:

    "#{post_path(post.root)}#post_#{post.id}"
    # => "/posts/42#post_69
    

    Stimulus Reference

    Stimulus is part of Hotwire, an alternative approach of building modern web apps. Turbo supposedly takes care of 80% of the usual JS required, while Stimulus should be able to handle the majority of other custom use cases.

    The Goals of Stimulus

    Unlike most other JavaScript frameworks, Stimulus, takes an HTML centered approach to handling state and page updates. The idea is to manipulate your existing HTML, instead of say, making an AJAX request and parsing JSON to determine which elements on the page should re-render. You use Stimulus to “sprinkle” interactive behavior into static/server-rendered HTML pages.

    Stimulus is concerned with manipulating this existing HTML document. Sometimes that means adding a CSS class that hides an element or animates it or highlights it. Sometimes it means rearranging elements in groupings. Sometimes it means manipulating the content of an element, like when we transform UTC times that can be cached into local times that can be displayed.

    There are cases where you’d want Stimulus to create new DOM elements, and you’re definitely free to do that. We might even add some sugar to make it easier in the future. But it’s the minority use case. The focus is on manipulating, not creating elements.

    The Dos and Don’ts

    Do

    • make your controllers small, concise, and re-usable.
    • combine controllers to create more complex behavior and interactions.
    • use as much as possible with Turbo.

    Don’t

    • make controllers too specific (with naming or functionality).
    • try to recreate your entire frontend with Stimulus (i.e. creating “view components”).
    • fall into old habits of using JQuery.

    Basics

    The important things I forget often. Otherwise, look at the official reference for details.

    Mainly be aware of:

    • Controllers
    • Actions
    • Targets

    Controllers

    Controllers are the JavaScript classes that house all the actual behavior of the HTML.

    Actions

    Actions are how you handle DOM Events in your controllers. Specifically, you can specify which DOM Event will execute a specified function inside your Stimulus controller.

    For example:

    <div data-controller="gallery">
      <button data-action="click->gallery#next"></button>
    </div>
    
    // controllers/gallery_controller.js
    import { Controller } from "@hotwired/stimulus"
    
    export default class extends Controller {
      next(event) {
        // …
      }
    }
    
    • You can have multiple actions tied to one HTML element.
    • Prefer naming action methods based on what will happen when they’re called (i.e. click->profile#showDialog)
    • Actions can have parameters that are be passed from the submitter element.
      • They use the format data-[identifier]-[param-name]-param.
      • Values will be typecast, so you can pass Arrays and Hashes.
      • Retrieve them in a controller with event.params.

    Targets

    Targets let you reference important elements by name.

    • Often, these targets are specific elements that need to be manipulated some way.
      • i.e. elements that you expect to have content loaded later.
    • Avoid using targets as a replacement for "action arguments".
    • Elements can have mutltiple targets and targets can be shared by multiple controllers.

    • Stimulus Handbook
    • Writing Better Stimulus Controllers