Today I Learned Notes to self about software development

    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

    Design Pattern: Presenter

    Philosophy

    • Views are for presentation.
    • There should be no ActiveRecord queries in views.
    • Most logic (if statements) should be excluded from views.

    Why Not Helpers?

    Helpers are better to use if you have a global formatting method that you re-use in different views.

    Things like:

    • rendering markdown
    • showing dates in a specific format
    • removing specific words from text

    Helpers are not great to overuse because they lack organization and are difficult to reuse across your app.

    Using Presenter Objects

    • create app/presenters.
    • name after model, app/presenters/post_presenter.rb.
    class PostPresenter
      def initialize(post)
        @post = post
      end
    
      def title_without_forbidden_words
        @post.title.gsub("forbidden word", "")
      end
    
      def css_color
        @post.draft? ? "orange" : "green"
      end
    end
    
    <% presenter = PostPresenter.new(post) %>
    
    <p>
      Post title: <%= presenter.title_without_forbidden_words %>
    
      <%= link_to "Read post", post, class: "w-75 p-3 text-#{presenter.css_color} border-#{presenter.css_color}" %>
    </p>
    

    This accomplishes a few things:

    • it removes logic from views
    • creates meaningful names for methods and logic
    • allows logic to more easily be reused in other views and mailers

    Rails Design Patterns: Presenter & Service Objects

    Unusual Case Statement Behavior

    TIL something weird.

    I expected this to print “Integer”, but it doesn’t:

    mystery_class = Integer
    case mystery_class
    when Integer
      p "Integer"
    else
      p "Unknown"
    end
    # => "Unknown"
    

    This was weird because a similar example appears to behave differently:

    mystery_instance = 0
    case mystery_instance
    when 0
      p "Y"
    else
      p "N"
    end
    # => "Y"
    

    Apparently this is because []case uses === under the hood](https://stackoverflow.com/a/3908411) and === has kind of silly behavior when comparing classes.

    For example:

    Array === Array # false
    0 === 0         # true
    Class === Array # true
    

    apparently case also works different with ActiveRecord classes, since they’ll use is_a? instead which might also behave differently??? 😱

    Anyway, if you want to use case with classes for ActiveRecord object you need to do this:

    mystery_class = User.last
    case mystery_class
    when User
      p "User"
    else
      p "Unknown"
    end
    # => "User"
    

    Rails Rollback to Specific Migration

    Occassionally, when working on an app with a lot of active branches that affect the database I run into an issue where I can’t rollback.

    I’ll get an error like this:

    rails aborted! ActiveRecord::UnknownMigrationVersionError:

    No migration with version number 20230620205505.

    This usually happens when I switch to another branch and I want to rollback a change I made to update/remove the migration. I assume the migration referenced in database doesn’t exist on the current branch.

    If this happens, you can roll back to the last migration on the branch with:

    rails db:migrate:down VERSION=n
    

    where n is the timestamp from the latest migration (something like 20230607135355).

    This is only applicable for non-sqlite3 databases, since the database doesn’t live in the project directory.

    Hiccup with Rails 7 Generation

    The server won’t work out of the box if you run the generate command with npm version < 7.1.

    In particular, with < 7.1, you have to add the build commands to the package.json scripts yourself.

    See this SO answer for more details.

    I had mistakenly generated an app with a npm v6.8 b/c I used n to switch NodeJS versions to debug a student assignment and apparently never switched back.

    I sure hope that hasn’t been the cause of other issues I ran into 😅