Today I Learned

A Hashrocket project

Ready to join Hashrocket? Find Openings here and apply today.

132 posts by dillonhafer

Enable key repeat in VSCode

Even if your operating system enables key repeat, VSCode will disable it. To turn it on you need to update a default value and restart vscode:

defaults write com.microsoft.VSCode ApplePressAndHoldEnabled -bool false
osascript -e 'tell application "Visual Studio Code" to quit'
osascript -e 'tell application "Visual Studio Code" to activate'

Disable broken VSCode feature

TL;DR in the command palette choose โ€œWorkspaces: Configure Workspace Trustโ€ and change โ€œStart Promptโ€ to โ€œneverโ€

VSCode has a nice security feature warning about the risks of unknown file authors, but it is too naive to be useful; it actually has the reverse effect of being insecure (due to the fact most of these folders have been used in vscode for years before this feature was introduced). For example, I have over 200 projects on my machine:

ls ~/dev | wc -l
     213

The start prompt is simply training me to continually click โ€œYes I trust this codeโ€ over and over again, not only does this have no effect, but actually has a negative effect of making VSCode think I gave careful consideration to the folder, when instead, I was just trying to open my files.

Increase size of ProgressView

You can increase the size of a ProgressView by using the .scaleEffect method:

AsyncImage(url: URL(string: video.poster_url)) { phase in
  if let image = phase.image {
      image
        .resizable()
        .aspectRatio(contentMode: .fill)
  } else if phase.error != nil {
    Color.red
  } else {
    ProgressView()
      .scaleEffect(x: 2, y: 2, anchor: .center)
  }
}
.frame(width: width, height: height)

Split a string in elixir

You can split a string in elixir with String.split

defmodule Phoenix.Repo.Migrations.CreateUsers do
  use Ecto.Migration

  def up do
    execute_sql("""
      create extension citext;

      create table users (
        id bigint generated by default as identity primary key,
        email citext unique not null,
        inserted_at timestamptz not null default now(),
        updated_at timestamptz not null default now()
      );
    """)
  end

  def down do
    execute_sql("""
      drop table users;
      drop extension citext;
    """)
  end
   

  def execute_sql(sql_statements) do
     sql_statements
     |> String.split(";")
     |> Enum.filter(fn s -> String.trim(s) != "" end)
     |> Enum.each(&execute/1)
  end
end

Replace first letter of string

You need to use runes to prevent corrupting unicode values:

package main

import (
    "fmt"
)

func replaceFirstRune(str, replacement string) string {
    return string([]rune(str)[:0]) + replacement + string([]rune(str)[1:])
}

func main() {
    name := "Hats are great!"
    name = replaceFirstRune(name, "C")
    fmt.Println(name)
}

Output:

=> Cats are great!

Just like in ruby, this doesnโ€™t cover multi-byte unicode characters. You still need to do a unicode table lookup:

name = "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ"
name[0] = "C"
=> "Cโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ"
println(replaceFirstRune("๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", "C"))
=> "Cโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ"

You can go step more and replace the man with a woman:

println(replaceFirstRune("๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", "๐Ÿ‘ฉ"))
=> "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ"

You MUST use a ws protocol in rails๐Ÿ’ฉactioncable

Railsโ€™ actioncable library is a bit immature compared to other implementations, so there are a lot of rough edges to work around. One of those is the basic createConsumer function.

If your app is running without a DOM (nodejs), the node_module @rails/actioncable is going to fight you.

The rails guides recommend this:

createConsumer('https://ws.example.com/cable')

But that function relies on having a global document that can create <a> tags, which you wonโ€™t have in many contexts (node, react-native, etc.)

Also, why does a websocket library depend on HTML anchor tags?

You can work around this limitation by explicitly using a ws or wss protocol:

createConsumer('ws://localhost:3000/cable')

Ruby's ENV::[]= only accepts strings

You cannot set the value of an environment variable to something that is not a string

# THIS DOES NOT WORK
ENV["SKIP_AUTH"] = true
=> `[]=': no implicit conversion of true into String (TypeError)

You can, however, pass an object that implements #to_str

class User < ApplicationRecord
  def to_str
    to_global_id.to_s
  end
end

ENV["user"] = User.first
ENV["user"]
=> "gid://rails-app/User/3f565b9c-0899-49f6-ab20-aa2724235ff5"

Be careful when stubbing ENV in specs:

# ENV could never return a boolean, your tests will lie to you.
RSpec.describe "ENV" do
  before do
    stub_const("ENV", {"SKIP_AUTH" => true})
  end
end

Have OS specific implementations in golang

In go, you can create separate implementations for different operating systems by creating multiple files:

package main

import (
    "play.ground/foo"
)

func main() {
    foo.HelloComputer()
}

-- go.mod --
module play.ground

-- foo/foo_windows.go --
// +build windows

package foo

import "fmt"

func HelloComputer() {
    fmt.Println("Hello windows!")
}

-- foo/foo_linux.go --
// +build !windows

package foo

import "fmt"

func HelloComputer() {
    fmt.Println("Hello not windows!")
}

Pass keyword arguments when using send

Donโ€™t use a hash, just pass send with a comma-separated list of keyword arguments:

class Animal < Struct.new(:name)
  def greet(name:, catch_phrase:)
    puts "Heya #{name}! What's new, #{catch_phrase}?"
  end
end

Animal.new("Rex").send(:greet, name: "Dillon", catch_phrase: "cool cat")
=> "Heya Dillon! What's new, cool cat?"

Sending with a hash will fail:

Animal.new("Rex").send(:greet, {name: "Dillon", catch_phrase: "cool cat"})
=> wrong number of arguments (given 1, expected 0; required keywords: name, catch_phrase) (ArgumentError)

How to use ActiveRecored.pluck without Arel.sql

It is too cumbersome to remember to wrap every string in a class method, so this is a shortcut:

# app/models/application_record.rb

class ApplicationRecord < ActiveRecord::Base
  module ::ActiveRecord::Sanitization::ClassMethods
    define_method(:original_disallow_raw_sql!, instance_method(:disallow_raw_sql!))

    def disallow_raw_sql!(args, permit: connection.column_name_matcher) # :nodoc:
      original_disallow_raw_sql!(args.map { |a| a.is_a?(String) ? Arel.sql(a) : a }, permit: permit)
    end
  end
end

Then you can write sql without the class methods

This can be easier to write:

User.where(payment_due: true)
  .pluck(Arel.sql("coalesce('last_billed_date', 'start_date')"))

Can now just be written as:

User.where(payment_due: true)
  .pluck("coalesce('last_billed_date', 'start_date')")

This can also be useful for .order as well

Rails has helpers for uploading spec fixture files

Instead of writing your own helper method in specs:

module HelpMePlease
  def uploaded_file(path)
    Rack::Test::UploadedFile.new(Rails.root.join("spec/fixtures", path))
  end
end

Rails has a built in helper method:

require "rails_helper"

RSpec.describe HelpNeeded do
  describe "something" do
    it "sends the file" do
      post :change_avatar, params: { avatar: fixture_file_upload("spongebob.png", "image/png") }
    end
  end
end

Add a folder to git with exceptions

Given this folder structure:

app
โ”œโ”€โ”€ bin
โ”‚  โ””โ”€โ”€ parse-ansi-codes.rs
โ”œโ”€โ”€ Cargo.lock
โ”œโ”€โ”€ Cargo.toml
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ src
โ”‚  โ”œโ”€โ”€ cursor.rs
โ”‚  โ”œโ”€โ”€ lib.rs
โ”‚  โ””โ”€โ”€ style.rs
โ”œโ”€โ”€ target
โ”‚  โ””โ”€โ”€ debug
โ””โ”€โ”€ test

I can add the entire app directory to git, while ignoring the bin folder:

$ git add . ':!bin'

rspec should receive thrice

rspec has a #thrice method for testing receive counts:

describe Account do
  context "when opened" do
    it "logger#account_opened was called once" do
      logger = double("logger")
      account = Account.new
      account.logger = logger

      logger.should_receive(:account_opened).thrice

      account.open
      account.open
      account.open
    end
  end
end

Replace multiple characters in ruby strings

Ruby String#tr allows you to replace characters or patterns in strings:

irb(main):001:0> "I love coffee".tr("love", "๐Ÿ˜")
=> "I ๐Ÿ˜๐Ÿ˜๐Ÿ˜๐Ÿ˜ c๐Ÿ˜ff๐Ÿ˜๐Ÿ˜"

Compare with #gsub:

irb(main):001:0> "I love coffee".gsub("love", "๐Ÿ˜")
=> "I ๐Ÿ˜ coffee"

If your pattern arg to gsub is only one character consider using #tr, but beware of multi-length from_str arg to #tr

Split text in postgres

You can split text in postgres

select email from users;
bob@example.com
mary@example.com
select split_part(email, '@', 1) from users;
bob
mary
select split_part(email, '@', 2) from users;
@example.com
@example.com

Which can be helpful when sanitizing data:

update users
  set email = split_part(email, '@', 1) || '@example.com';

List cli xcodebuild archives in Xcode's organizer

When using a Makefile to build Xcode applications, itโ€™s nice to have the archives listed in the Organizer window, for easy distribution. This can be accomplished with the -archivePath flag, using a specific directory:

BOB_THE_BUILD_DIR="~/Library/Developer/Xcode/Archives/$(date +%Y-%m-%d)"
ARCHIVE_PATH="$BOB_THE_BUILD_DIR/MyApp-$(date|md5).xcarchive"

xcodebuild -scheme MyApp -workspace "MyApp.xcworkspace" archive -configuration release -archivePath "$ARCHIVE_PATH"

๐Ÿšฏ Prevent development logs from bloating on macOS

I rarely need to refer to development.log or test.log when working on rails applications, but yet I end up keeping weeks or even years of records [gigabytes]. Iโ€™m used to working with logrotate, and I wanted to find a similar solution that was preinstalled with macOS. macOS comes preinstalled with a program called newsyslog that can keep file sizes in check. I just created a new file at /etc/newsyslog.d/rails.conf which limits all railโ€™s log files to just 10MB

# /etc/newsyslog.d/rails.conf
# logfilename                                                   [owner:group]      mode count size(KB)     when  flags [/pid_file] [sig_num]
/Users/<username>/dev/<rails projects>/*/log/*.log              <username>:staff   644  0     10000         *     G
# Mono repos
/Users/<username>/dev/<rails projects>/*/*/log/*.log            <username>:staff   644  0     10000         *     G
# Deeper mono repos
/Users/<username>/dev/<rails projects>/*/*/*/log/*.log          <username>:staff   644  0     10000         *     G

You can also perform a dryrun of the config for testing:

sudo newsyslog -v -n -f /etc/newsyslog.d/rails.conf

What directory is the parent of root ๐Ÿ‘จโ€๐Ÿ‘ฆ๐Ÿ“

I learned that in unix, root (e.g. /) is the only directory that is the parent directory of itself.

$ ls -lai / | grep '\./'
                  2 drwxr-xr-x   20 root  wheel   640 Jan  1  2020 ./
                  2 drwxr-xr-x   20 root  wheel   640 Jan  1  2020 ../

In the above example, the files . and .. both have the same i-node: 2

Source: Brian W. Kernighan, & Rob Pike (1984) The UNIX Programming Environment. Prentice-Hall, Inc

prevent execution when creating materialized views

When working with foreign data wrappers, one uses a materialized view to store the downloaded foreign table data. The process of downloading could be very expensive and managed by another process or program, but your program needs to define the parameters of materialzied view/foreign table. Maybe the data is loaded out of band by cron:

@daily /usr/bin/psql -d cool_db -c 'refresh materialized view big_view;'

So to create the materialized view without loading the data we use the WITH NO DATA clause:

create foreign table measurement_y2016m07
    partition of measurement for values from ('2016-07-01') to ('2016-08-01')
    server server_07;

create materialized view big_view
  as select *
  from measurement_y2016m07
  with no data;

This way we are able to execute the create foreign table and create materialized view statements in a very short amount of time. A different process can start the download with refresh materialized view

Fix poor type support in immutablejs

Immutablejs doesnโ€™t work very well with typescript. But I can patch types myself for some perceived type safety:


import {Map,fromJS} from 'immutable';

interface TypedMap<T> extends Map<any, any> {
  toJS(): T;
  get<K extends keyof T>(key: K, notSetValue?: T[K]): T[K];
}

interface User {
  name: string;
  points: number;
}

const users = List() as List<TypedMap<User>>;

users.forEach(u => {
  const aString = u.name
  const aNumber = u.points
});

Postgres Identity Column

The Postgres wiki recommends not using the serial type, and instead added identity columns to replace them.

Old way:

create table todos (
  id bigserial primary key,
  todo text not null
);

The new way with identity columns:

create table todos (
  id bigint generated by default as identity primary key,
  todo text not null
);

Data:

insert into todos (todo) values
  ('write a til'),
  ('get some coffee');

select *
from todos;
 id |      todo
----+-----------------
  1 | write a til
  2 | get some coffee
(2 rows)

Source: PG wiki: Donโ€™t use serial