Today I Learned

hashrocket A Hashrocket project

23 posts by briandunn twitter @higgaion

Expo Go over VS Code Live Share shared servers

VS Code Live Share is a pretty sweet way to remote pair. It supports Shared Servers, a fancy way to forward ports without exchanging ssh keys. Unlike ssh port forwarding, however, there isn’t a way for the joiner to choose which of their local ports will be used. The port selection is random.

That doesn’t cut it with Expo Go (and maybe vanilla react-native?) which demands the port be the same for the server and the client. So I looked for a way to forward one port on my local machine, the random one chosen by Live Share, to another port on my machine - the expo default of 19000.

Thanks to socat that was easy:

 socat tcp-listen:19000,reuseaddr,fork tcp:localhost:

I opened exp://localhost:19000 in Expo Go in the simulator. 🤘 Sweet.

Export CSV of query without COPY

Clients task me to export data to a CSV from time to time. My favorite workflow for authoring queries is a split tmux session, vim (with the excellent coc-sql) on one side, and a psql session on the other. I modify my query and then re-execute via \i.

This is great, until I’m ready to export the CSV. historically I’ve edited the query, cumbersomely breaking my REPL flow by turning it into a COPY command. Now I do this:

\pset format csv
\o output-file.csv
\i my-query.sql

To reset my psql session output…

\pset format aligned

And thus my-query.sql remains a working query.

Change the CodeLens annotation color in CoC

CoC is the fanciest Language Server Protocol client for (neo)vim. It even implements Code Lens annotations, like reference counts in Javascript:

code lens reference counts

Ok neat. But by default, virtual text in neovim is in the normal highlight group. That means it looks just like code, and I’m already confused enough. After much source diving, I found that the highlight group used by CoC for this is CocCodeLens, and used like so:

:hi CocCodeLens guifg=White

code lens reference counts with better color

My code is much more readable.

fill your quickfix window with lint

File names I can’t jump to frustrate me. Today I ran $ npx eslint and my computer said “I looked at a file, and found a problem on this line, in this column. Do you want to see it? Good for you. Go type out the file name in your editor then."

ButI wanted a jump list of all the eslint errors in my project. Eslint is a kind of compiler, right? Vim knows compilers.

:set makeprg=npx\ eslint\ -f\ unix

Now I can


and behold!


I can now see all of the errors and warnings for the project, and nimbly jump betwixt.

Parallel xargs fails if any of its children do

We like to write about xargs. In addition to all that, turns out xargs is a great tool for easily parallelizing tests, linters or anything where some may pass, and some may fail. If any of the processes that xargs spawns fail, the xargs call will also fail.

All child processes exit zero:

% echo "0\n0\n0" | xargs -Icode -P4 sh -c 'exit code'; echo exit code: $?
exit code: 0

And so does xargs! If any exit non-zero:

echo "0\n1\n127" | xargs -Icode  -P4 sh -c 'exit code'; echo exit code: $?
exit code: 1

xargs follows suit.

Don't async await, especially in useEffect

Often I need to setState based on an async value. With hooks this is something like:

useEffect(async () => {
    const newVal = await asyncCall();

But wait!. This throws an error. React wants the return of useEffect to be a cleanup function.

The return type of an async function is Promise. So that won’t work. Best to just put on your big developer pants and use that promise.

useEffect(() => {

There. Now our useEffect returns undefined, and React is pleased.

Here is a sandbox if you want to see for yourself.

ProxyJump: simplest way to ssh with a jump host

Ever need to jump your ssh through an intermediate host? You may be familiar with using netcat like this in your ~/.ssh/config:

Host jumpy
  ProxyCommand ssh -q jump-host nc destination-host %p

ProxyCommand runs on our local machine. The command must open a tcp connection that ssh may then use for the session. Here we shell into our jump-host, connecting the file descriptors to nc, which will forward all data to destination-host on %p, the port you provided to -p on the cli.

Clear as mud, no?

Another slightly more readable way to achieve this is with -W

Host jumpy
  ProxyCommand ssh -W destination-host jump-host

This works the same way as the nc version, but now we are using ssh‘s internal implementation of nc. So one less dependency on the jump host.

But behold, the most legit and legible version of jumping hosts:

Host jumpy
  Hostname destination-host
  ProxyJump jump-host

So same thing, but now the words say what it does.

When you get lost remember that .ssh/config has it’s own man page:

$ man ssh_config

Thanks Dillon!

Animate ReactNative in ClojureScript with Reagent

Reagent uses ratoms to refresh components. When the state of a ratom is changed, components that deref that state re-render. ReactNative Animation uses a similar technique: changes to an Animated.Value cause a re-render. I was afraid these concepts would collide, and I’d have to bypass Reagent using it’s low-level api to use animations. But much to my joyful surprise, Animated.Value changes trigger a re-render in normal Reagent components.

(ns my-app.animation
    ["react-native" :as ReactNative]
    [reagent.core :as r]))

(def animated (.-Animated ReactNative))
(def text (r/adapt-react-class (.-Text ReactNative)))
(def animated-value (.-Value animated))
(def animated-view (r/adapt-react-class (.-View animated)))

(defn testview []
  (let [bounce-value (new animated-value 0)]
    (.setValue bounce-value 1.5)
    (-> bounce-value
        (animated.spring #js {:toValue 0.8 :friction 1})
    (fn [] [animated-view
            {:style {:flex 1
                     :transform [{:scale bounce-value}]
                     :background-color "red"
                     :border-radius 10
                     :shadow-color "#000000"
                     :shadow-opacity 0.7
                     :shadow-radius 2
                     :shadow-offset {:height 1 :width 0}}}
            [text "test"]])))


This may look complex, but compare it to the low level Reagent api used in other examples.

Enable gzip for all phoenix responses

Enabling gzip for static assets in phoenix couldn’t be simpler. In lib/myapp_web/endpoint.ex change gzip from false to true:

 plug(Plug.Static, at: "/", from: :tilex, gzip: true, only: ~w(assets ...))

But why stop there? We can compress our dynamic document bodies just as easily. In config/prod.exs, add compress: true to the http config of the endpoint.

config :my_app, MyAppWeb.Endpoint,
  http: [port: {:system, "PORT"}, compress: true]

Rails on ruby 2.4: Silence Fixnum/Bignum warnings

Wanna use the latest ruby, but don’t really need to be perpetually reminded that rails hasn’t caught up with this latest ruby design change? Change the top of your config/application.rb thusly:

require_relative 'boot'

verbose = $VERBOSE
$VERBOSE = nil
require 'rails/all'
require 'active_support/core_ext/numeric/conversions'
require 'active_job/arguments'
$VERBOSE = verbose

module MyApp
  class Application < Rails::Application

Yes, this will also suppress any other ruby warnings from the loading of rails code. ¯\_(ツ)_/¯

Postgres, varchar, and silent white space trimming

Given this table:

# create table initials (monogram varchar(2) primary key);

Insert a three character monogram.

# insert into initials(monogram) values ('BPD');
ERROR:  value too long for type character varying(2)

Ok, cool. Thanks Postgres. But what if my string ends in a space?

# insert into initials(monogram) values ('BP ');

What? This caused me great pain when trying to do an insert. Here in my example I’ve used a CTE for brevity, but imagine you can’t see the input table, and it contains thousands of records. My insert went something like this:

with input as (
        select 'BD '::text as data
insert into initials(monogram)
select data from input where not exists (select 1 from initials where monogram =
ERROR:  duplicate key value violates unique constraint "initials_pkey"
DETAIL:  Key (monogram)=(BD) already exists.

A spot check based on the error message turns up no results:

select count(*) from input where data = 'BD';
(1 row)

But of course 'BD ' = 'BD', if you keep in mind that Postgres will silently truncate trailing whitespace when casting to a varchar.

Rerun only failed specs with tmux

You don’t want to run your whole sweet again, just those failures?

Copy the bit below Failed examples: into your tmux buffer:

rspec ./spec/mailers/order_mailer_spec.rb:5 # OrderMailer reciept includes the support email and phone number
rspec ./spec/features/customer_checks_out_spec.rb:43 # customer checks out happy path

Now make command line args for rspec out of it:

tmux showb | ag -o '[^\s]+:\d+' | tr '\n' ' ' > rerun.txt

Run only this set of specs with xargs:

xargs rspec < rerun.txt