Today I Learned

hashrocket A Hashrocket project

Report Code Statistics in Rails

You can run rails stats to print out statistics around lines of code, classes, methods, code to test ratio, etc for your rails app. It'll print out a table like this:

image

(Don't mind the code to test ratio...it's a proof of concept app 😅)

h/t Joel Hawksley, I learned about this from the excellent talk he gave at Chicago Ruby last week.

Nested Expectations in RSpec

Today I learned you can write nested expectations in rspec. I find this is approach useful with writing request-style tests where I want to ensure the request returned successfully and also ensure the expected effects happened.

it "creates the post" do
  expect {
    expect(request.response_code).to eq(200)
  }.to change { Post.count }.by(1)
end

If either the response is not 200, or the Post count doesn't change, then the test fails.

There is a gotcha that I run into when I build these tests iteratively - first the inner expectation on it's own, wrap it in the outer block, and then add the outer matcher. If you wrap the inner request in an expect block, but don't have any assertions on that block, it will always pass - because we're not matching against anything.

RSpec.describe do
  # ❌ - as expected
  it do
    expect(true).to eq(false)
  end

  # ❌ - as expected, outer expectation passes but inner fails, so test fails
  it do
    expect {
      expect(true).to eq(false)
    }.to not_change { 0 }
  end

  # ✅ - :-? watch out for this one, even though the inner expectation fails, test passes
  it do
    expect {
      expect(true).to eq(false)
    }
  end
end

Connecting to github via ssh with different users

Due to github permissions on projects that I am working on I wanted to switch which user I am connecting to github on a per project base. I also want to keep using ssh keys for that, so I found out that we can have 2 different ssh keys pointint to the same domain, we just had to do a simple twich:

Host github.com
    HostName github.com
    IdentityFile ~/.ssh/id_ed25519

Host vinny.github.com
    HostName github.com
    IdentityFile ~/.ssh/vinny_id_ed25519

So I end up creating 2 ssh keys, 1 per user that I need to access on github. Then left the original ssh key as regular github.com domain, so no changes there, and on the projects that I need to access with the other user I had to clone with:

git clone git@vinny.github.com:MyOrganization/my-project.git

And if you have an exisintg project that you want to move to this new github user you'll have to change the remote on that:

git remote remove origin
git remote add origin git@vinny.github.com:MyOrganization/my-project.git

By the way, as you are changing which keys to use with an existing domain, you may have to restart the ssh-agent in order for this to work:

eval "$(ssh-agent -s)"

Expo Router add index page into stack

We had a situation that we wanted to guarantee that an "index" page under a tab navigation was always there, this way we could go back using the navigation to the index page, even if the user entered to that tab navigation via another screen. I learned that we can use withAnchor prop in conjunction with the unstable settings initialRouteName. This worked really fine and now our back buttons always go to each tab's index screens.

Check this out Expo Router navigation patterns

Randomly Sort Rows in Postgres

I'm on a bit of a random kick lately. Today I learned you can randomly sort rows returned in a query using Postgres' random() function.

SELECT *
FROM users
ORDER BY random()

What it does is it calculates a random value for each row returned, and then orders by that calculated value. If the query returns a lot of rows this can be slow, so probably more useful for exploration or one-offs not necessarily for production uses.

Use jot to Print Sequences

Today I Learned about jot, a command line utility that prints sequential or random data. Let's focus on the sequential bit.

jot takes arguments for reps, begin and end. So if we wanted to print the integers from 1 to 10, we'd do:

$ jot - 1 10
1
2
3
4
5
6
7
8
9
10

Which is neat, but only so cool. What if we wanted to print 21 evenly spaced numbers between -1 and 1? That would be cooler:

$ jot 21 -1 1.00 #the 1.00 tells the output to be float, not int
-1.00
-0.90
-0.80
-0.70
-0.60
-0.50
-0.40
-0.30
-0.20
-0.10
-0.00
0.10
0.20
0.30
0.40
0.50
0.60
0.70
0.80
0.90
1.00

You can even print all the ASCII characters with:

jot -c 128 0
1
2
3
4
5
6
7
8
9
:
;
<
=
>
?
@
A
B
C
...

Echo substrings of a variable

Say you have a variable set in a shell script:

export testPath='/Users/mary/code/thingy.rb'

You can echo that variable as usual:

echo $testPath
# /Users/mary/code/thingy.rb

But you can also get substrings of that variable as you echo it, using substring extraction. The substring extraction syntax is ${variable:offset:length}

echo ${testPath:11}
# /code/thingy.rb

echo ${testPath:12:4}
# code

You can also change the prefix (${variable#prefix}) and suffix (${variable%suffix}):

echo ${testPath#/Users/mary/code/}
# thingy.rb

echo ${testPath%.rb}
# /Users/mary/code/thingy

Random Sort Text

You can sort to sort text in a file or STDIN. But what if you want to embrace the chaos and sort randomly? Turns out, there's a few ways to do that.

  • sort -R file.txt - sort's -R option does a random sort rather than lexicographically. The caveat here is if you have duplicates, they will be grouped together in the resulting output.
  • shuf file.txt - shuf is a standalone utility to generate random permutations.

Read their man pages to learn more!

Autosave belongs_to Associations

By default, if you make a change to a belongs_to associated model and save the parent model, the associated model won't be saved. This is probably a good default, but it is overridable with the :autosave option.

class Book < ApplicationRecord
  belongs_to :author
end

book = Book.first
book.author.name # => "J.R.R. Tolkein"

book.author.name = "John Tolkein"

book.save
book.reload
book.author.name # => "J.R.R. Tolkein"

But we can change this behaviour by setting :autosave to true.

class Book < ApplicationRecord
  belongs_to :author, autosave: :true
end

book = Book.first
book.author.name # => "J.R.R. Tolkein"

book.author.name = "John Tolkein"

book.save
book.reload
book.author.name # => "John Tolkein"

While this is off by default for belongs_to associations, has_one and has_many associations have :autosave true because the foreign keys are on the associated records to ensure those FKs are kept up to date.

Build rows from jsonb data in PostgreSQL

PostgreSQL has a function called jsonb_to_recordset that will return your jsonb data as if it were rows in the database.

Say we have the following example table:

create table notes (title varchar, bullets jsonb);

insert into notes (title, bullets)
values ('Inspection', '[{"field": "Tires", "measured": "Tread Height", "value": "3mm"},{"field": "Brakes", "measured": "Caliper Boot", "value": "cracked"}]');

To use the jsonb_to_recordset function, we can do the following:

select title, field, measured, value
from notes
cross join lateral jsonb_to_recordset(notes.bullets) 
as temp(field varchar, measured varchar, value varchar);

The function gives us the following output:

   title    | field  |   measured   |  value  
------------+--------+--------------+---------
 Inspection | Tires  | Tread Height | 3mm
 Inspection | Brakes | Caliper Boot | cracked

Place a cursor at ALL occurrences in Visual Studio

One of the nice things about working with Visual Studio based text editors is its multi-cursor abilities. For those who don't know, you can highlight a word and pressing CMD+D you can place another cursor at the next occurrence of that string.

This is helpful for making a lot of changes at once. Here's an example where we can fix a typo that was made all throughout a file:

typo = getTypo

if(typo)
    # do something with the typo
else
    # do something else
end

def getTypo()
  typo
end

Well, what if we want a cursor at every occurrence of a typo, but we want a faster way than pressing CMD+D a bunch of times?

TLDR
Just highlight the word you want to grab and press CMD+Shift+L and it will grab all occurrences found in that file. (Just be careful and make sure that's what you actually want!)

Reload All Buffers in Vim

In vim :e edits the current file (reloads from disk), which is useful if the file has been changed outside of vim and you want to load those changes into your buffer in vim.

I ran into a scenario where I ran a formatter outside of vim, and wanted to reload all files open in buffers in my vim session. I took a guess that :ea would edit all (like :wa writes all, :qa quits all) - but alas, swing and a miss.

The way to do this is with :bufdo e. bufdo executes the command - in this case e - in each buffer in the buffer list.

Read Man Pages in (n)vim

Reading man pages in (n)vim is my new favorite way to read man pages. (n)vim gives you syntax highlighting, plus you can follow the links with Shift + K (yes, there are links between man pages - I never knew!).

You can override the default pager for man by setting the MANPAGER environment variable (the default is less -sR) - per the nvim help page, you can use nvim with export MANPAGER='nvim +Man!'.

Happy manual reading!

h/t Josh Branchaud, the TIL GOAT.

Validating Keyword lists in Elixir

I found out that Keyword.validate/2 is a great way to guarantee that a function would be called with the right keyword options. Check this out:

def do_something(%User{} = user, opts \\ []) when is_list(opts) do
  {:ok, opts} = Keyword.validate(opts, [:preload])
  preload = Keyword.get(opts, :preload, [])
  #...
end

Here's some interesting cases:

# fails if you pass an invalid option
Keyword.validate([one: "1", two: "2"], [:one])
# => {:error, [:two]}

# fails if you pass a valid option that is not expected that many times
Keyword.validate([one: "1", one: "again"], [:one])
# => {:error, [:two]}

# it can set default values
Keyword.validate([one: "1"], [:one, two: "5"])
# => {:ok, [two: "5", one: "1"]}

# it succeeds if you pass the right options
Keyword.validate([one: "1", two: "2"], [:one, :two])
# => {:ok, [two: "2", one: "1"]}

Prevent display from falling asleep on MacOS

If you want to prevent your display from going to sleep while you are doing something that doesn't require input, you can use caffeinate -d

For me this was useful when running a longer script that I wanted to keep an eye on while I was debugging. By running caffeinate -d in a terminal I was able to prevent the display from sleeping, saving me from pressing a button or moving the mouse all the time.

git commit -C

Today I learned about the -C flag for git commit. It's used in Hashrocket's dotmatrix in our git cheddar alias - git commit --amend -CHEAD, but I never actually dug into what it does.

From the manual:

-C <commit>, --reuse-message=<commit>

Take an existing commit object, and reuse the log message and the authorship information (including the timestamp) when creating the commit.

Important to note, -C doesn't open your editor to edit the message, so it's great if you don't need to edit the existing message (if you do want to edit, lowercase -c will open your editor with the existing message).

-CHEAD....ohhhh that's why it's called cheddar 🤯

ActiveRecord List All Enums

Today I learned a way to view all the enums defined on an ActiveRecord Model.

Suppose I have a class Post with enums status and category:

class Post < ApplicationRecord
  enum status: [:draft, :published]
  enum category: [:ruby, :rails, :lord_of_the_rings]
end

I can view the enum values for statuses with Post.statuses and category with Post.categories, but what if I want to see all the enums on Post? defined_enums will do that:

Post.defined_enums
# => {"status"=>{"draft"=>0, "published"=>1}, "category"=>{"ruby"=>0, "rails"=>1, "lord_of_the_rings"=>2}}

You can of course key in to each enum here too:

Post.defined_enums["status"]
# => {"draft"=>0, "published"=>1}

Extract Secrets from 1Password for Kamal Deploy

Kamal, the default deployment tool for Rails, has some really great features. One that I just discovered today is kamal secrets.

You can use this utility to expand sensitive credentials from external sources during deployment. Out of the box, it supports 1Password, Bitwarden (and Secrets Manager), LastPass, AWS Secrets Manager, Doppler, and GCP.

You can run the command for SECRETS from the .kamal/secrets file manually to test everything out.

A pre-req for using 1Password is that you will need to install the OP CLI and login to your vault:

brew install 1password-cli

op signin

Next you'll need your account id. You can get that with -

op whoami

Then verify you can read your secrets. The output of the command inside the $(...) is a stringified JSON -

SECRETS=$(kamal secrets fetch --adapter 1password --account op_account_id --from "op://Example/ApiKeys" KAMAL_REGISTRY_PASSWORD)

The output will look something like this -

\{\"Example/ApiKeys/KAMAL_REGISTRY_PASSWORD\":\"xxxxxxxxxxxxx\"\}

The last part is expanding this. You can pass this JSON string to kamal secrets extract to extract the value from the key in the JSON.

kamal secrets extract KAMAL_REGISTRY_PASSWORD ${SECRETS}

Change your current context

You can change the context of your console session by using the cd just like you would change the context of your directory.

Here is an example.

# Grab an object, in our example a customer.
c = Customer.all.sample

# Now, 'cd' in to that object.
cd c

# From here you will notice that we are now within 
# the context of our object and can call methods on it directly.

pry(<Customer>):1> first_name
=> "Peter"
pry(<Customer>):1> last_name
=> "Parker"

I found this cool tip, as well as some others in this awesome blog post.

Screenshot Single Window in macOS

It feels like there are an absurd number of options with the built in macOS screenshot tool.

We've seen before you can screenshot the entire screen or select a rectangle and save them as a file or to your clipboard.

Today I Learned there's an option to select an individual window and screenshot the entire window.

You can do this by hitting CMD + shift + 4 (with or without ctrl), then tap space. The cursor will change to a camera, and then you can click on the windows you wish to screenshot!

image

Looks much better than when I try to crop by hand.

Handy Commands for Raspberry Pi Sensors

I recently got an Enviro hat for my Raspberry Pi Zero W. And in tinkering with it, I found there's a ruby driver for interacting with the BME280 sensor.

You can use it like this -

require "i2c/device/bme280"
require "i2c/driver/i2c-dev"

I2CDevice::Bme280.new(driver: I2CDevice::Driver::I2CDev.new("/dev/i2c-1")).read_raw

In debugging, I learned a few useful commands to check the i2c port. Basically, I just needed to figure out what i2c port to use for the ruby driver.

Check if the i2c port is enabled (raspberry pi os)

sudo raspi-config nonint get_i2c # 0 if enabled, 1 if disabled

List all the i2c interfaces by lsing the dev node.

ls /dev/i2c*

Check if the i2c board interface is in the list of kernel modules

lsmod | grep i2c

3 ways to clear the terminal

Option 1:
Simply typing the terminal command clear will clear your terminal. This method preserves your current terminal session, meaning you can scroll up to see what was cleared away.

Option 2:
Pressing CTRL+L to clear away the terminal. This behaves essentially the same as the clear command, preserving your terminal session.

Option 3:
Pressing CMD+K to clear away the terminal. This behaves similarly to the other 2 options, except that it does not preserve your session.


h/t Vinicius Negrisolo

Run a rake task from within your Rails application

I was curious if I could trigger a rake task from within my Rails application.
After some digging, I found Rails::Command.invoke() which lets you run rake tasks from inside your Rails app.

Here's an example using db:migrate:

Rails::Command.invoke('db:migrate')

Just pass it the name of your task (db:migrate, your_custom_task, etc) and it will begin processing it.

This is great for automating or triggering rake tasks during bootstrapping or background operations without needing to leave the Rails environment.

Deleting Words in the Terminal

I stumbled upon this earlier today, if you're in a terminal and have typed some things in, pressing Esc, then Backspace will delete the word on or before your cursor. Ctrl + W will do the same thing.

So if I start typing a word and want to

$ one two three four 
                    ^ cursor here

Then Esc, Backspace will produce

$ one two three
               ^ cursor here

It deletes through the beginning of the word, but starts where the cursor is, so you can delete partial if your cursor is in the middle of a word

$ longer command here
            ^ cursor here

Then Esc, Backspace will produce

$ longer and here
         ^ cursor here

Confirmed both work in recent versions of bash and zsh.

Tables in Markdown

To create tables in a markdown file, you can use this syntax:

| Header 1 | Header 2 | Header 3|
| --- | --- | --- |
| Content 1 | Content 2 | Content 3 |
| Content 4 | Content 5 | Content 6 |

And this will output something like this, depending on what you are using to interpret the markdown:

image


I find this to be useful when displaying side-by-side images in a Github PR

Here is a silly example:

Post Office (B&W) Post Office (Colorized)
image image

h/t Vinicius Negrisolo

Run Script on Login/Start in Windows

You can automatically run a script on Windows during login by placing files in your users "Programs\Start Up" folder. It's useful for running *.bat scripts.

You can easily access this location by opening the "Run" dialog - Windows + r and typing shell:startup.

If you're an admin, you can also set this for all users by typing shell:common startup, which will open the shared Start Up folder.

If this is too much, you can also accomplish a similar thing by creating a "Scheduled Task". In the Windows search bar, type "Task Scheduler". Open this then click "Actions" -> "Create Task"

Count Commits within Time Range in Git

git rev-list lists commit objects in reverse chronological order, and as such is an incredibly powerful tool for inspecting your repository.

For example, if you wanted to count all your commits, you can with git rev-list --count HEAD.

You can also drill down to a specific time range with the --before and --after options. To get the count of all commits during 2024, you can run:

git rev-list --count --after="2024-01-01" --before="2025-01-01" HEAD

There are tons of other options you can use to filter this down too, by author, committer, etc.

Omitting specific fields in TypeScript types

TypeScript has an Omit utility type that lets you create a new type by excluding specific keys from an existing type. It's perfect for cases where you need a type that's almost the same but without certain properties.

Here's an example:

type User = {
  id: number;
  name: string;
  password: string;
};

type PublicUser = Omit<User, 'password'>;

Now we have a PublicUser type that includes id and name, but the password field is excluded.

h/t Vinicius Negrisolo

Change font size in iPhone simulator from keyboard

We found it very annoying to navigate to settings>accessibility>display>larger text just to verify that things still look good on phones with larger font sizes.

Turns out you can adjust the font size of the simulator much quicker using the following keyboard shortcuts:
CMD+Option++ to increase the font size of the device
CMD+Option+- to decrease the font size of the device

NOTE!
Just keep in mind, even if you have Larger Accessibility Sizes toggled off, you can still get to the larger font sizes this way.

Get React Native Component's Dimensions

In React Native you can use the onLayout prop along with the nativeEvent's layout to get the dimensions (in pixels) of a rendered component.

const [viewDimensions, setViewDimensions] = useState({height: 0, width: 0})
return (
  <View
     onLayout={(e) => setViewDimensions(e.nativeEvent.layout)}
  />
)

Now we have an object containging our view's height and width saved as viewDimensions

ActiveRecord.invert_where

Ever been debugging in ActiveRecord and wanted to inverse a query? You can do that without changing the whole query!

User.where(active: true)
# => "select * from users where active = 'true'"

User.where(active: true).invert_where
# => "select * from users where active != 'true'"

It also works with multiple conditions:

User.where(active: true, subscribed: false)
# => "select * from users where active = 'true' and subscribed = 'false'"

User.where(active: true, subscribed: false).invert_where
# => "select * from users where active != 'true' and subscribed != 'false'"

It works on scopes, too:

class User < ActiveRecord::Base
  scope :active, -> { where(active: true) }
end

User.active.invert_where
# => "select * from users where active != 'true'"

Managing Return Value of ActiveRecord Transactions

I recently ran into a situation where I wanted to know the return state of a transaction in ActiveRecord. Basically, I wanted to know if it succeeded or failed, then return true/false.

There's 3 scenarios to keep in mind to when trying to use this approach:

  1. Transaction succeeds - the last line of the transaction is the return
    class TestModel
     def save
       result =
         ActiveRecord::Base.transaction do
           SomeModel.last.touch
         end
    
       result
     end
    end
    
    > TestModel.new.save
    => true
  2. Transaction rollbacks - transactions handle ActiveRecord::Rollback by default, so the transaction block returns nil
    class TestModel
     def save
       result =
         ActiveRecord::Base.transaction do
           raise ActiveRecord::Rollback
         end
    
       result
     end
    end
    
    > TestModel.new.save
    => nil
  3. Other Errors - any errors raised inside the transaction will be raised by the block
    class TestModel
     def save
       result =
         ActiveRecord::Base.transaction do
           raise ActiveRecord::Rollback
         end
    
       result
     end
    end
    
    > TestModel.new.save
    StandardError: StandardError from .....

Putting it all together now, we can return a true/false for all scenarios -

class TestModel
    def save
      result =
        ActiveRecord::Base.transaction do
          SomeModel.new(attrs).save!
          OtherModel.new(other_attrs).save!
        end

      result

      did_save = !!result
      did_save
    rescue => e
       false
    end
end

Rails Generate Tasks

Andrew's TIL from the other day got me thinking about rails generators, and I never knew there was a generator for rake tasks!

There's not a ton of boilerplate in rake tasks, but that's never stopped me from making a typo.

bin/rails g task hello will generate just the namespace hello

# lib/tasks/hello.rake
namespace :hello do
end

If you have one or more tasks within the namespace, you can include them and the generator will add a task for each one:

bin/rails g task say hi bye good_day will generate

# lib/tasks/say.rake
namespace :say do
  desc "TODO"
  task hi: :environment do
  end

  desc "TODO"
  task bye: :environment do
  end

  desc "TODO"
  task good_day: :environment do
  end
end

Rails 8 Generate One Off Scripts

Rails 8 added a new generator command for creating 1 script files. The docs encourage the use of this folder for one off tasks, data migrations, etc. Previously, you could have used rake tasks to accomplish a similar thing.

Running this generator command will create the script dir if one does not already exist.

❯ rails g script migration_script
      create  script/migration_script.rb