Today I Learned

hashrocket A Hashrocket project

Custom email routing in Github

Is your Github account attached to multiple organizations? Does your primary address associated with Github get inundated with emails/notifications you don't care to find in your personal inbox?

Let me help you out by sorting your Github notification life. Get an email address to which you can flow relevant notifications and associate them with your Github account. Then head on over to Github > Settings > Notifications > Custom Routing

Click "Add new route" and choose an organization and an email address associated with your account.

Github custom routing settings page for notifications

As you can see, I have my primary email address with Github. Still, now all relevant notifications from Hashrocket projects will funnel into my Hashrocket email address.

Easily skip tests in Jest

Using skip in Jest tests is a valuable technique for temporarily disabling specific tests without completely removing them from your test suite. This is particularly useful during development when you focus on one particular feature or bug and want to avoid running unrelated tests to save time. In Jest, you can use skip by prefixing it to your test or describe blocks.

For instance, test.skip(...) or describe.skip(...) will skip the execution of the specified test or group of tests. This approach is much more maintainable than commenting out tests, as it keeps the test code intact and easily readable.

Moreover, skipped tests are reported in Jest's output, reminding you that there are tests that need attention. This feature is convenient when working in a team environment, as it allows developers to be aware of tests that are not currently active but are still part of the overall test plan. It also helps ensure that all tests are eventually re-enabled and the test coverage remains comprehensive.

String Concat Pattern Matching In Elixir

This is a neat pattern matching trick in elixir, its best explained with a simple example:

invoice_message = "You owe $34"
"You owe " <> dollar_amount = invoice

IO.inspect(dollar_amount)
# => "$34"

With a slightly different situation, It may seem like you could do this:

invoice_message = "You owe 34 dollars"
"You owe " <> dollar_amount  <> " dollars"= invoice

IO.inspect(dollar_amount)
# => ** (ArgumentError) the left argument of <> operator inside
# a match should always be a literal binary because its size can't be verified. Got: dollar_amount

But sadly you'll need to use regex to that because elixir will throw an error.

Only Pick certain properties in Typescript

In TypeScript, the Pick utility type provides a flexible way to construct new types by selecting subsets of properties from existing ones. For example, consider an interface User:

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

// Using Pick to create a new type type
UserContactDetails = Pick<User, 'email' | 'name'>;

In this example, UserContactDetails is a new type with only the email and name properties from the User interface.

This is particularly beneficial when you must pass or manipulate only specific aspects of a complex type, ensuring type safety and reducing the risk of handling unnecessary or sensitive data. By tailoring types to particular use cases, Pick enhances code maintainability and readability, streamlining development processes in TypeScript applications.

saved_changes & previous_changes in Rails

In Ruby on Rails, saved_changes and previous_changes are methods used to track changes in Active Record models. saved_changes is used after an object is saved to the database. It provides a hash of all the attributes changed when persisting an object, including their original and final values. This method helps understand what changes have just persisted. On the other hand, previous_changes is used after an object is saved but before reloading. It holds the same information as saved_changes but becomes available only after the save operation and before the object is reloaded. It is helpful for actions triggered immediately after a save, like callbacks or logging changes.

Both methods are instrumental in tracking attribute changes and responding to them effectively in a Rails application.

Verify current password with has_secure_password

Now with Rails 7.1, has_secure_password can now automatically verify the current password when updating the password. This is useful to check if the user who is trying to update the password, knows the current password:

class User < ActiveRecord::Base
  has_secure_password
end

user = User.new(password: "sekret", password_confirmation: "sekret")
user.save
#=> true

user.update(password: "HAHAHA", password_challenge: "")
#=> false, challenge doesn't authenticate

user.update(password: "updated*sekret", password_challenge: "sekret")
#=> true

Mock External Library Functions in Jest

Suppose you import two functions from an external library in one of your js files:

import { func1, func2 } from "externalLib";

export const doSomething = () => {
  func1();
  func2();
  // ...
};

Now let's write a test for this function, and say we want to mock func1.

describe("doSomething", () => {
  jest.mock("externalLib", () => ({
    func1: jest.fn(),
  }));

  it("does something", () => {
    func1.mockImplementation(...);
    // ...
  });
});

But oh no! This test will fail - we inadvertently blew away the rest of the implementation of externalLib when we created the mock for func1. Since doSomething relies on other functions in externalLib, we get errors.

In order to preserve the rest of the implementation of externalLib and only mock func1, we need to call requireActual and spread it's return into the mock to get the implementation of the rest of the library:

describe("doSomething", () => {
  jest.mock("externalLib", () => ({
    ...jest.requireActual("externalLib"),
    func1: jest.fn(),
  }));

  it("does something", () => {
    func1.mockImplementation(...);
    // ...
  });
});

Et voilà, your test will pass ✅.

Running a Single Jest Test

Jest runs tests in parallel by default, file by file.

That means if you add a .only on an it or describe, that only applies in the context of a single file. So if you have a test suite with 5 different test files and execute tests with jest, 4 test files will run all their tests and one file will run it's only test.

If you want only a single test to run, you need to add that .only and narrow jest's scope to that single test file: jest src/path/to/test.js.

Styling your host from within shadow dom

The :host declaration in CSS is a functional pseudo-class used in Web Components, specifically within Shadow DOM. When you create a custom element with Shadow DOM, the :host pseudo-class allows you to define styles that will apply to the host element of the shadow tree. This means you can style the custom element rather than just its content. For instance, if you create a custom <my-element> tag and attach a shadow DOM to it, using :host in your CSS (inside the shadow DOM) lets you set properties like background color, margin, or padding directly on <my-element>. This is particularly useful for encapsulating styles in web components, as it allows the component to have its own isolated styling that doesn't bleed into the rest of the page or get affected by external styles.

ActiveRecord's attribute_in_database method

Wanted to share a method that I learned about today.

ActiveRecord has a handy utility for checking the value of an attribute in the database. These methods are actually on the Dirty module, and as such, are intended to be used when checking during validations or callbacks before saving.

These particular methods will read from the database (as their name implies), instead of using the value currently in memory.

There's 2 different methods to be aware of that you can use like below -

class Invoice < ActiveRecord::Base
  validates :amount, presence: true
end

invoice = Invoice.last
invoice.amount
=> 10.0

invoice.amount = 80.0

invoice.attribute_in_database("amount")
=> 10.0

invoice.amount_in_database
=> 10.0

ActiveRecord::AttributeMethods::Dirty Docs

Health endpoint added by default in Rails 7.1

A newly generated Rails 7.1 app will now add an endpoint to your routes file to act as a heartbeat. You can point to many services or monitoring tools to check for downtime.

get "up" => "rails/health#show", as: :rails_health_check

However, this is just an indicator of the application running. If you want to do anything more advanced, like checking if the database is up... feel free to write your own. Ultimately, this is a great addition and will work in most situations.

Auto remove and sort JS imports (VS code)

Say you have a few unused imports in your JS file:

import charlie from "@library/charlie";
import alpha from "@library/alpha";
import beta from "@library/beta";       #UNUSED
import echo from "@library/echo";
import delta from "@library/delta";     #UNUSED

If you're using Visual Studio Code (or a forked version like Cursor) you can press Option/Alt+Shift+O to remove all unused imports.

import alpha from "@library/alpha";
import charlie from "@library/charlie";
import echo from "@library/echo";

Note: this also sorts your current imports

Git Log an Individual Function

Today I learned you can print the git log for an individual function with the -L flag - -L :<funcname>:<file>.

So if I have a function named friendlyFunction in app/fileName.ts, I can see it's history with:

% git log -L :friendlyFunction:app/fileName.ts
commit 740ab0c1eeb63e0a6bef06f89397e76407325f58 (HEAD -> main)
Author: Tony Yunker
Date:   Mon Nov 6 17:22:52 2023 -0600

    last commit

diff --git a/app/fileName.ts b/app/fileName.ts
--- a/app/fileName.ts
+++ b/app/fileName.ts
@@ -1,4 +1,4 @@
 export const friendlyFunction = () => {
-  console.log("hi there 👋");
+  console.log("bye now 👋");
   return 2 + 2;
 };

commit 314e725e56871e17c64b811de8e8cdb65badb08e
Author: Tony Yunker
Date:   Mon Nov 6 17:22:31 2023 -0600

    second commit

diff --git a/app/fileName.ts b/app/fileName.ts
--- a/app/fileName.ts
+++ b/app/fileName.ts
@@ -1,4 +1,4 @@
 export const friendlyFunction = () => {
   console.log("hi there 👋");
-  return 1 + 1;
+  return 2 + 2;
 };

commit 61998a2a9dfb64b0ee3aa5c23f783cca517ab15b
Author: Tony Yunker
Date:   Mon Nov 6 17:22:08 2023 -0600

    first commit

diff --git a/app/fileName.ts b/app/fileName.ts
--- /dev/null
+++ b/app/fileName.ts
@@ -0,0 +1,4 @@
+export const friendlyFunction = () => {
+  console.log("hi there 👋");
+  return 1 + 1;
+};

Docs

Custom Sigils In Elixir

You can create custom sigils by following the sigil_{character} definition pattern. Let's make an addition sigil sigil_a that sums up space separated numbers.

defmodule CustomSigil do
  def sigil_a(string, []) do
    string
    |> String.split(" ")
    |> Enum.map(& String.to_integer(&1))
    |> Enum.sum()
  end
end

# ~a(2 4 6)
#=> 12
# ~a(12 12)
#=> 24

Lib folder now auto-loaded again in Rails 7.1

Many years ago, the lib folder was removed from being auto-loaded in Rails. However, in Rails 7.1 it's back.

You can add autoload_lib to your config, and the lib folder will be added to your application's autoload paths. It does accept an ignore argument, which allows you to, of course, ignore folders in lib. When your new project is generated, it will add the assets & tasks folder to be ignored.

config.autoload_lib(ignore: %w(assets tasks))

There is a caveat that autoloading of lib is unavailable for Rails engines.

Declare Node version for a project

In a Node.js project, versions of Node that a project is compatible with can be declared using the "engines" key within the package.json file. To set a specific Node version (or a range of versions), you'll use the node property inside the engines object.

For example, to specify that your project is compatible with Node version 14.15.1, you would include the following:

{ 
  "engines": { "node": "14.15.1" }
}

If you want to indicate compatibility with various versions, you can use semantic versioning notation. For instance, "node": ">=14.0.0 <15.0.0" means your project is compatible with versions of Node greater than or equal to 14.0.0 but less than 15.0.0. By setting the engines key, you hint to other developers and deployment platforms about which Node versions should be used with your project. Note, however, that unless you use a package manager that enforces this (e.g., Yarn or newer versions of npm), this is primarily advisory and won't prevent the project from running on non-specified versions.

Module aliases using Typescript in Next.js

Importing a module can be lengthy, depending on your project's app structure.

import { Widget } from "../../../../components/widget";

Utilizing your tsconfig, aliases can be created for module resolution.

{
  "compilerOptions": {
    "paths": {
      "@components/*": ["./app/components/*"],
    }
  }
}

Now, our previous example can be expressed like so:

import { Widget } from "@components/widget";

Next.js is already set up to deal with this, so you should not need to add any additional libraries like tsc-alias to handle the actual module resolution when the project is built for production.

Execute NPM binary without installing package

When working with command-line tools, it's common to encounter scenarios where you'd like to test or run a tool without committing to a global installation, especially for one-off tasks. The npx utility addresses this exact need. By invoking npx <command>, the tool automatically fetches and installs the package corresponding to <command> from the npm registry if it's not already in your $PATH. This allows you to execute the desired command seamlessly. A significant advantage is that post-execution, npx ensures the package is not retained as a global installation, preventing unnecessary clutter or "pollution" in your global packages. This utility offers a cleaner, transient approach to testing and using CLI tools.

Pre-defined ActiveStorage variants in Rails 7.1

Rails 7.1 adds the ability to use pre-defined variants when calling preview or representation on an attachment.

class User < ActiveRecord::Base
  has_one_attached :file do |attachable|
    attachable.variant :thumb, resize_to_limit: [100, 100]
  end
end

<%= image_tag user.file.representation(:thumb) %>

Here, we declare a thumb variant that can later be referenced—no need to explicitly express the resize_to_limit directly at the image_tag call.

Push commits with repo creation using Github CLI

When creating a GitHub repo with the CLI tool, you have the opportunity to push up your commits immediately as well using the --push or (-p)

❯ gh repo create OWNER/REPO --push --source .
✓ Created repository OWNER/REPO on GitHub
✓ Added remote git@github.com:OWNER/REPO.git

Using the push flag requires the --source flag to tell it where the repo is locally which is why you see that in the example above.

Create remote with repo creation using GitHub CLI

If you have not partaken, creating a GitHub repo with the CLI tool is pretty cool; however, one thing that always got me was that when you create a new repo it does not create a remote so I'd always have to go back and make an origin.

There is an additional CLI flag that you can pass to do this for you.

gh repo create OWNER/REPO -r origin -s .

So, just for context, the -r (or --remote) flag is for naming the remote. This must also be used with the -s (or --source) flag, which tells the CLI where the repo lives on your local system. In the above example, I referenced the directory I was in using a ..

Create new repository with GitHub CLI

There's a good chance you're using GitHub if you're a developer. A long while ago, GitHub wrapped git commands with a tool they released called hub. This was nice, then, as it helped bridge the gap between Git and GitHub. A few years ago, they released the GitHub CLI, a dedicated command line tool for GitHub... no more wrapping Git commands.

There are many things you can do with this tool, but the one I use the most is setting up a new repository.

With this simple command, you can generate the repo on GitHub.

> gh repo create OWNER/REPO
✓ Created repository OWNER/REPO on GitHub

CSS text-wrap now with more pretty

There are several ways to get your text to wrap with css. However it always seems that there ends up being an orphan piece of text on the wrap. The newly introduced pretty option for text-wrap aims to fix this.

A comparison of a paragraph with orphans and one with no orphans, each with a badge of bad or good.

Image sourced from Why you should remove orphans from your body text.

Like i mentioned earlier it is a new feature and is not fully supported in all browsers yet but definitely a welcome addition.

See Which Files Changed in git

git log is great to see a detailed diff, but what if you only want to see the files that were changed?

There's git whatchanged, but it's essentially deprecated, and git docs recommend using git log instead:

% git whatchanged
commit 9925b8ec8fc8024544a36c71e210a23192a63bf4 (HEAD -> main, origin/main, origin/HEAD)
Author: Tony Yunker <tony.yunker@gmail.com>
Date:   Fri Oct 20 15:07:09 2023 -0500

    ruby - abbreviated assignment operators

:100644 100644 7d41153 7449733 M    README.md
:000000 100644 0000000 5a432d1 A    ruby/rubys_abbreviated_assignment_operators.md

So then we can use git log --name-only to see the names of the files that were changed.

% git log --name-only
commit 9925b8ec8fc8024544a36c71e210a23192a63bf4 (HEAD -> main, origin/main, origin/HEAD)
Author: Tony Yunker <tony.yunker@gmail.com>
Date:   Fri Oct 20 15:07:09 2023 -0500

    ruby - abbreviated assignment operators

README.md
ruby/rubys_abbreviated_assignment_operators.md

But that won't tell us if they were updates or adds or whatnot. For that, we can use git log --name-status

% git log --name-status
commit 9925b8ec8fc8024544a36c71e210a23192a63bf4 (HEAD -> main, origin/main, origin/HEAD)
Author: Tony Yunker <tony.yunker@gmail.com>
Date:   Fri Oct 20 15:07:09 2023 -0500

    ruby - abbreviated assignment operators

M   README.md
A   ruby/rubys_abbreviated_assignment_operators.md

These flags can be used on git show too!

Log into Github CI server using SSH

Have you ever needed to log into your Github CI run? Is it a flaky test that's hard to reproduce, and the logging output could be more helpful?

You can log into the CI run while it's going. Try out the Github action Debugging with SSH. Utilizing upterm and Tmux, allows for a session that can be logged into.

Add a step like this to your workflow:

name: CI
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Setup upterm session
      uses: lhotari/action-upterm@v1

A cool feature is that it lets you lock down who can log in via SSH keys. Add a limit-access-to-* declaration like so:

name: CI
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Setup upterm session
      uses: lhotari/action-upterm@v1
      with:
        ## limits ssh access and adds the ssh public key for the user which triggered the workflow
        limit-access-to-actor: true
        ## limits ssh access and adds the ssh public keys of the listed GitHub users
        limit-access-to-users: githubuser1,githubuser2

Then, you can log in via your workflow's 'checks' panel.

h/t JackC

Site-specific searching from the address bar

In Chromium based web-browsers (Chrome, Brave, etc.) go into your settings and search for site search.

Once you find the settings, click on Add under Site Search. This will give you a form like so:

image

Search engine is the name that will be displayed in the address bar when searching

Shortcut is the string you type in the address bar that will begin the site-specific searching

URL is the desired search url, putting %s where your search terms will go.

For example, searching through all of the TILs can be done with

https://til.hashrocket.com/?q=%s

Now with this in place, you should be able to something like the following, to quickly view all TILs on a certain topic:

til vim

image

Now you can enjoy easily learning about the thousands of things all the Hashrocketeers have learned for themselves over the years!

Column alias for ActiveRecord select in Rails 7.1

Previously, when writing an ActiveRecord select and having the need to add a column alias, you'd have to provide a SQL fragment string like so:

Customer.select("customers.name AS customer_name")

=> SELECT customers.name AS customer_name FROM "customers"

Rails 7.1 added a more robust hash syntax for selects that also allows for column aliasing.

Customer.joins(:orders).select(name: :customer_name)

=> SELECT "customers"."name" AS "customer_name", FROM "customers"

Shorthand syntax for ActiveRecord select Rails 7.1

Using the newly available select syntax in Rails 7.1 we can express a SQL select like so:

Customer.joins(:orders).select(customers: [:name], orders: [:total])

=> SELECT "customers"."name", "orders"."total" FROM "customers" INNER JOIN "orders" ON "orders"."customer_id" = "customers"."id"

There's also some shorthand you can utilize to make it even shorter. We can still add column names as Symbols, as we've always been able to. They will reference the Model the select is being performed on.

Customer.joins(:orders).select(:name, orders: [:total])

=> SELECT "customers"."name", "orders"."total" FROM "customers" INNER JOIN "orders" ON "orders"."customer_id" = "customers"."id"

We removed the need to declare the customers table as it is implied from being called on the Customer relation.

Caveat: since declaring other columns is done via a hash, that must be provided as the last argument. You couldn't say something like .select(:id, orders: [:total], :name).

Hash syntax for ActiveRecord select in Rails 7.1

Previously, when writing an ActiveRecord select and wanted to add columns from anything outside of the model you originated from, you'd have to provide a SQL fragment string like so:

Customer.joins(:orders).select("customers.name, orders.total")

=> SELECT customers.name, orders.total FROM "customers" INNER JOIN "orders" ON "orders"."customer_id" = "customers"."id"

Notice above how we declare columns from the customers table and the orders table.

As of Rails 7.1, you can now stay in the ActiveRecord DSL and provide hash key/values. The query example above can directly be expressed as:

Customer.joins(:orders).select(customers: [:name], orders: [:total])

=> SELECT "customers"."name", "orders"."total" FROM "customers" INNER JOIN "orders" ON "orders"."customer_id" = "customers"."id"

You provide the table names as keys and the column names you want to select as Symbols in an Array.

Also, notice that the selected columns in the query are appropriately quoted vs the SQL fragment above.

Ruby's Abbreviated Assignment Operators

Today I Learned ruby has a lot of abbreviated assignment operators.

The best known are += and -= to increment and decrement values:

x = 2
x += 1
x #=> 3

And of course there's ||=, to assign only if the value is nil or false:

x = nil
x ||= 4 #=> 4
x ||= 5 #=> 4

But these abbreviations can be applied to a lot more operators!

It works with all of the following: +, -, *, /, %, **, &, |, ^, <<, >>, &&, ||.

So we could use |= to union two arrays and assign the result to the variable:

x = [1, 2, 3]
x |= [2, 3, 4, 4]
x #=> [1, 2, 3, 4]

Autofill last argument from previous commands

Have you ever found yourself typing out long commands in the terminal, only to need a part of that command in a subsequent command? You can save time by reusing the last argument of the previous command using the ESC key followed by a period .

Here's an example:

First, you use cat to display the contents of the file example.txt:

cat path/to/your/files/example.txt

Now, if you want to perform an operation on the same file, you don't need to type out the entire path again. Instead, use the ESC key followed by the period .

vim ESC. 

# After pressing ESC. this becomes: 
vim path/to/your/files/example.txt

In this example, ESC. automatically inserts the last argument from the previous command, which is the file path path/to/your/files/example.txt.

Note: You can use ESC . repeatedly to cycle through previous arguments in your command history.

How to remove timestamp on screenshots in MacOS

When you take screenshots on MacOS they are saved with a rather long name, like Screenshot 2023-10-18 at 4.13.56 PM

To remove the timestamp making the names shorter just enter this in your terminal:

defaults write com.apple.screencapture "include-date" 0

After doing that go ahead and run this:

killall SystemUIServer

Now your screenshots will just be saved as Screenshot and successive screenshots in the same directory will have a number appended to them. (i.e Screenshot 1, Screenshot 2, etc.)

Expanded routing info in Rails

When using rails and wanting to know how one of your routes plays out, it's very easy to do a quick cli call to rails routes. This gives an overview of all routes in the app.

> rails routes
Prefix             Verb  URI Pattern             Controller#Action
rails_health_check GET   /up(.:format)           rails/health#show
restaurants        GET   /restaurants(.:format)  restaurants#index {:format=>:json}                      

However if we provide the --expanded flag, we get a more verbose output with also tells you exactly where in the routes file it was declared.

> rails routes --expanded
--[ Route 1 ]--------------------------------------------------------------------
Prefix            | rails_health_check
Verb              | GET
URI               | /up(.:format)
Controller#Action | rails/health#show
Source Location   | config/routes.rb:6
--[ Route 2 ]--------------------------------------------------------------------
Prefix            | restaurants
Verb              | GET
URI               | /restaurants(.:format)
Controller#Action | restaurants#index {:format=>:json}
Source Location   | config/routes.rb:11

This could be exceptionally helpful when breaking up large routing files in Rails

Style elements on data attributes in Tailwind

Using Tailwind you can apply custom styling to an element based on it's data attribute.

For example, given these elements:

<div data-state="checked" class="data-[state=checked]:bg-black bg:white">
  <!-- Html that could change the data-state goes here -->
</div>

<div data-state="unchecked" class="data-[state=checked]:bg-black bg:white">
  <!-- Html that could change the data-state goes here -->
</div>

Of these two elements, the first one would be given a black background, while the second one would have a white background, unless it's data-state changes to checked.

How to change the screenshot location on MacOS

By default, screenshots on MacOS are saved to the Desktop. To change this to a specific directory:

First, bring up the screenshot utilities by pressing CMD + SHIFT + 5.
Next, click on the Options selector.
Finally, choose a location under Save to

Note: Here is where you can set your screenshot to be on a timer, choose whether or not you want the mouse/cursor to show up on the screenshots, and a change few other options.