Today I Learned

A Hashrocket project

56 posts by dillon ๐Ÿ„

Convert .mov to .gif with ffmpeg

Sometimes putting a gif in a pull request is really helpful for reviewers. If youโ€™ve recorded a movie on an iOS simulator with xcrun simctl or just QuickTime, itโ€™s very simple to convert them to animated .gifs

Example:

ffmpeg -i screen_recording.mov \
       -s 415x925 \
       -pix_fmt rgb24 \
       -r 10 \
       -f gif \
       screen_recording.gif

Now it would be nice to have a function that could extract the video dimensions automatically ๐Ÿค” mdls ๐Ÿคฏ

1 function gif-mov() {
2   movie=$1
3   height=$(mdls -name kMDItemPixelHeight ${movie} | grep -o '[0-]\+')
4   width=$(mdls -name kMDItemPixelWidth ${movie} | grep -o '[0-9]\+')
5   dimensions="${width}x${height}"
6   ffmpeg -i ${movie} -s ${dimensions} -pix_fmt rgb24 -r 10 -f gif ${movie}.gif
7 }

Usage:

$ gif-mov ~/Desktop/cool-screen-recording.mov

Tuples are never inferred in TypeScript

If I have a function that takes a number tuple:

type Options = {
  aspect: [number, number],
  ...
}

const pickImage = (imageOptions: Options) => (
  ...
)

This will give a type error:

const myOptions = {
  aspect: [4, 3],
};

// โŒ Expected [number, number], got number[]
pickImage(myOptions);

I must use type assertion when passing tuples:

const myOptions = {
  aspect: [4, 3] as [number, number],
};

pickImage(myOptions);

Apollo-React hooks can easily refetch queries

React-apollo hooks allow you to easily refetch queries after a mutation, which is useful for updating a list when you have create/update/delete mutations.

useMutation takes an argument called refetchQueries that will run queries named in the array.

import React from "react";
import { useMutation } from "@apollo/react-hooks";
import gql from "graphql-tag";
import { Button } from "react-native";
import { Item } from "./types";

const ITEM_DELETE = gql`
  mutation ItemDelete($id: ID!) {
    itemDelete(id: $id) {
      id
    }
  }
`;

interface Props {
  item: Item;
}

const DeleteItemButton = ({item}: Props) => {
  const [deleteItem] = useMutation(ITEM_DELETE, {
    variables: { id: item.id },
    refetchQueries: ["GetItemList"],
  });

  return (
    <Button title="Delete" onPress={() => deleteItem()} />
  )
}

Push git branch to another machine

Whenever Iโ€™m working in the same git repo on multiple machines, and I need to work on a branch on both machines I usually push the branch to a shared remote for the sole purpose of pulling it down on the other machine. This third machine can be avoided by push directly between machines:

$ myproject(feature-branch): git remote add machine2 ssh://machine2:/Users/dillon/dev/myproject
$ myproject(feature-branch): git push machine2 feature-branch

Over on machine2:

$ myproject(master): git branch
* master
  feature-branch       <= ๐Ÿ˜ฏ

How to expose entire Swift class to Objective-C

Use the @objcMembers annotation in your Swift class. When working on a project that is migrating from Objective-C to Swift you will most likely be sharing all Swift functions with Objective-C, but that could start to look ugly:

class MyClass: NSObject {
  @objc func fancy() -> String {
    return "fancy"
  }
  
  @objc func tooFancy() -> String {
    return "๐Ÿคต"
  }
}

But if everything needs to be available to Objective-C, we simply do:

@objcMembers
class MyClass: NSObject {
  func fancy() -> String {
    return "fancy"
  }
  
  func tooFancy() -> String {
    return "๐Ÿคต"
  }
}

How to remove NotificationCenter observer in Swift

Use the method deinit. In Objective-C classes I would always remove NSNotificationCenter observers in the -dealloc method, but a Swift class doesnโ€™t have a -dealloc method. Instead, Swift has a deinit method.

Here is an example of Objective-C:

@implementation MyClass
  - (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
  }
@end

And the equivilant in Swift:

class MyClass {
  deinit {
    NotificationCenter.default.removeObserver(self)
  }
}

Open iOS simulator app data directory

If you need to quickly open an appโ€™s document directory on the iOS Simulator, you can quickly get the path from the xcrun CLI.

xcrun simctl get_app_container booted ${bundle_id} data

Hereโ€™s some ruby you can throw into a Rakefile of your iOS app ๐Ÿ˜‰

# Rakefile
desc "Open the simulator document directory"
task :docs do
  bundle_id = File.read("MyAppName/MyAppName.xcodeproj/project.pbxproj")
    .scan(/PRODUCT_BUNDLE_IDENTIFIER = "(.*)"/)
    .flatten.first

  app_directory = `xcrun simctl get_app_container booted #{bundle_id} data`

  puts "Opening simulator directory: #{app_directory}"
  `open #{app_directory}`
end

๐Ÿ” Using NSArray with CONTAINS NSPredicates

NSPredicateโ€™s predicateWithFormat method takes a va_list of arguments, so itโ€™s not possible to pass an array to your format string. But, the same result can be achieved by combining multiple NSPredicates together using an NSCompoundPredicate:

Given a space-separated array of search words:

NSArray<NSString*> *words = [@"my search terms" componentsSeparatedByString:@" "];

You can combine them by first creating multiple predicates:

NSMutableArray<NSPredicate *> *predicates = [NSMutableArray new];
for (NSString *word in words) {
  NSPredicate *predicate = [NSPredicate predicateWithFormat:@"attribute CONTAINS[cd] %@", word];
  [predicates addObject:predicate];
}

And finally create one NSPredicate via NSCompoundPredicate

NSPredicate *finalPredicate =
        [NSCompoundPredicate andPredicateWithSubpredicates:predicates];

Shell parameter expansion

Shells can perform variable expansion, this is really useful for default argument variables in shell functions.

Here is a simple example of assigning a variable from an argument or defaulting to running a different shell command:

clog() {
  log_date=${1-`date +%Y-%m-%d`}
  git log --after="$log_date 00:00" --before="$log_date 23:59"
}

This allows me to write

clog to default to todayโ€™s date or

clog 2018-10-02 to look at the logs from yesterday

The date in the example clog 2018-10-02 would come through as $1 in the function, so ${1-`date`} means run this shell command if there is no $1.

*references:

https://www.tldp.org/LDP/abs/html/parameter-substitution.html#USAGEMESSAGE

Client Connection Vars with Ecto

Postgres has dozens of connection variables it will take, a couple of my favorite ones are statement_timeout and application_name.

Ecto takes a parameters connection option, which is a keyword list of connection parameters.

# dev.exs

...

# Configure your database
config :myapp, Myapp.Repo,
  adapter: Ecto.Adapters.Postgres,
  database: "myapp_dev",
  socket_dir: "/var/run/postgresql",
  parameters: [application_name: "My App Development", statement_timeout: "5000"],
  pool_size: 10



Example of statement_timeout being respected in Ecto:

Ecto.Adapters.SQL.query!(Myapp.Repo, "select pg_sleep(86400)")

** (Postgrex.Error) ERROR 57014 (query_canceled): canceling statement due to statement timeout
    (ecto) lib/ecto/adapters/sql.ex:200: Ecto.Adapters.SQL.query!/5


Client Connection Variables references:https://www.postgresql.org/docs/current/static/runtime-config-client.html

Use a unix socket with Ecto

Ecto allows the use of a unix socket to connect to postgres, instead of TCP. In order to use a unix socket in a exs file, you should remove the hostname list item, and provide the socket_dir list item.

# dev.exs

...

# Configure your database
config :myapp, Myapp.Repo,
  adapter: Ecto.Adapters.Postgres,
  database: "myapp_dev",
  socket_dir: "/var/run/postgresql",
  pool_size: 10

NODE_OPTIONS without node

I was using storybook and found it had grown so large that its node process would run out of memory before it could start. The downside was that the storybook command did not accept node arguments, like --max_old_space_size=4096

Starting with node 8, node will look for options in an environment variable named NODE_OPTIONS to be interpreted as if they had been specified on the command line before the actual command line (so they can be overridden).

Example to give storybook more memory:

NODE_OPTIONS=--max_old_space_size=4096 storybook start

*References:
https://storybook.js.org
https://nodejs.org/dist/latest-v8.x/docs/api/cli.html#cli_node_options_options

๐Ÿ”ข CLI to bump iOS target versions

Bumping Info.plist versions by hand can be very error prone (e.g. remembering to bump multiple targets in a project)

Fortunately Xcodeโ€™s cli has an easy solution

xcrun agvtool new-marketing-version <versString>

So if my Application targets are at version 2.0.4 and I need them to be at 2.0.5 I simply run this command at the terminal:

xcrun agvtool new-marketing-version 2.0.5

*References: https://developer.apple.com/library/content/qa/qa1827/_index.html

๐Ÿ”‘ Set foreign keys to null

Sometimes, in certain circumstances, it is reasonable to have a foreign key value of null.

ActiveRecordโ€™s .has_many method has an argument to set the foreign key column on referencing rows to null when that record is deleted.

dependent: :nullify

Example:

class Post < ApplicationRecord
  belongs_to :author
  belongs_to :category
end

class Category < ApplicationRecord
  has_many :posts, dependent: :nullify
end

In this example whenever a category is deleted, any posts referencing the categories table will have their foreign key set to null.

*References: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many-label-Options>

Custom Browser with Create React App

Sometimes Google Chrome isnโ€™t your default browser for surfing the internet, but it is your default browser during development. With create-react-app you can specify an alternate browser to automatically open (or no browser at all), to avoid opening the wrong development browser all the time.

Just pre-pend the browser to the start script:

{
  "name": "custom-browser",
  "version": "0.1.0",
  ...
  
  "scripts": {
    "start": "BROWSER='Google Chrome' react-scripts start",
    ...
  }
}

Find host and port in development

Rails 5.1 is a little different the pre 5.1

require 'rails/commands/server/server_command'

In rails 5.0 or below

require 'rails/commands/server'
# lib/host_and_port.rb

def __host_and_port__
  options = Rails::Server::Options.new.parse!(ARGV)
  options.values_at(:Host, :Port)
end

You can then find the host and port for various configuration files.

# config/initializers/carrier_wave.rb
require Rails.root.join('lib/host_and_port').to_s

CarrierWave.configure do |config|
  config.asset_host = "http://" + __host_and_port__.join(":")
end

or

# config/environments/development.rb
require Rails.root.join('lib/host_and_port').to_s

host, port = __host_and_port__
config.action_mailer.default_url_options = { host: host, port: port }

Rails/PG Statement Timeout ๐Ÿ˜โฐ

By default, Rails does not set a timeout on database statements. For example, this will run for a full day, even if your ruby process goes away.

ActiveRecord::Base.connection.execute(<<~SQL)
  select pg_sleep(86400);
SQL

The good news is that Rails provides a way to prevent this from happening from the database.yml with the statement_timeout variable.

default: &default
  adapter: postgresql
  ...
  variables:
    statement_timeout: 5000

Iโ€™m starting to think that this should always be set low. And then explicitly set higher on a per-query basis, when one is expecting something to take a long time.

group by 1, 2, 3 ๐Ÿ”ข

Postgres allow group by and order by to reference column order in a sql statement. This is particularly useful when an aggregate result needs to be referenced in the group by or order by statement.

-- group by aggregate
select
  (created_at at time zone 'UTC' at time zone 'America/Chicago')::date,
  count(*)
from posts
group by (created_at at time zone 'UTC' at time zone 'America/Chicago')::date
order by (created_at at time zone 'UTC' at time zone 'America/Chicago')::date
;

becomes

-- group by 1, 2, 3
select
  (created_at at time zone 'UTC' at time zone 'America/Chicago')::date,
  count(*)
from posts
group by 1
order by 1
;

Open images in vim with iTerm ๐Ÿ–ผ

iTerm 3 has a built in shell script called imgcat for displaying images in the terminal. With one simple autocmd in my vim configuration I can open images*:

:autocmd BufEnter *.png,*.jpg,*gif exec "! ~/.iterm2/imgcat ".expand("%") | :bw

In my command I wipe the image buffer(:bw) because I donโ€™t want large images sitting around in buffers, but this is easy to change.

imgcat

*imgcat does not work with tmux

Use PostgreSQL socket in database.yml ๐Ÿ˜๐Ÿ”Œ

When using the host: configuration option in the database.yml set to localhost or 127.0.0.1, I would need to add an entry to PostgreSQLโ€™s pg_hba.conf file to allow my ip address access. But, if you give the host: option the directory of your PostgreSQL sockets, rails will be able to use the socket, without needing to add an entry to the PostgreSQL configuration file.

production:
  database: fancy_things
  host: '/var/run/postgres'
  ...

Drop connections with Nginx

You can ignore requests sent to nginx by responding with the status 444

server {
  listen 80;
  listen [::]:80;
  listen 443;
  listen [::]:443 ssl http2;

  server_name dillonhafer.com;
  location /no-response {
    return 444;
  }
}

The primary use for this is to prevent processing requests with undefined server names:

# block for requests with undefined server names
server {
  listen 80;
  listen [::]:80;

  return 444;
}

Respect Do Not Track ๐Ÿ‘โ€๐Ÿ—จ

Before I put google analytics, adwords, or other third party scripts on my sites I can respect the DNT headers a user has set. Hereโ€™s how I can do it with Google Analytics:

<script>
  if (window.navigator.doNotTrack !== '1') {
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

    ga('create', 'UA-xxxxxxx-5', 'auto');
    ga('send', 'pageview');
  }
</script>

In the above snippet I check if the user has enabled DNT. If they havenโ€™t set the header or they have turned it off the script will run. Checking for window.navigator.doNotTrack !== '1' ensures that the user purposely set the header.

Chaining TLS Certificates ๐Ÿฑ

Apache allows you to declare an intermediate TLS certificate along with your regular certificate in your configuration, but many web servers only allow you to provide one certificate option. Like Nginx, go, or heroku.

In those cases, you will need to concatenate the entire certificate chain into one certificate file. This may sound daunting, but the process is very simple. Let me introduce cat -- concatenate and print files.

cat is normally used for printing files, but in this case we actually want to concatenate files. Below is a simple example on how we can do this:

cp example_com.crt example_com.chained.crt
cat AddTrustExternalCARoot.crt >> example_com.chained.crt
cat COMODORSAAddTrustCA.crt >> example_com.chained.crt
cat COMODORSADomainValidationSecureServerCA.crt >> example_com.chained.crt

To shorten it, we only need to use cat once:

cp example_com{,.chained}.crt &&
cat AddTrustExternalCARoot.crt COMODORSAAddTrustCA.crt COMODORSADomainValidationSecureServerCA.crt >> example_com.chained.crt

Verify TLS cert with private key

Hopefully youโ€™re never in a situation where you donโ€™t know what private key you used to generate your TLS certificate, but if you doโ€ฆ hereโ€™s how you can check.

Note: this is better than uploading the certs to production to check on them ๐Ÿ˜‰

Assuming we have generated a private key named example.com.key and a certificate named example.com.crt we can use openssl to check that the MD5 hashes are the same:

openssl x509 -noout -modulus -in example.com.crt | openssl md5
openssl rsa -noout -modulus -in example.com.key | openssl md5

To make things better, you can write a script:

#!/bin/bash
CERT_MD5=$(openssl x509 -noout -modulus -in example.com.crt | openssl md5)
 KEY_MD5=$(openssl rsa  -noout -modulus -in example.com.key | openssl md5)
 
if [ "$CERT_MD5" == "$KEY_MD5" ]; then
  echo "Private key matches certificate"
else
  echo "Private key does not match certificate"
fi

Trust Issues ๐Ÿค”

Our computers trust a scary amount of Root Certificate Authorites, and sometimes I have trust issues with some of them. Most recently being the StartCom bug, which allowed anyone to get a certificate for any domain they wanted.

I canโ€™t trust them. Period. And I donโ€™t have to.

Here is how you can revoke trust for any Root CA in OSX:

  1. Open Keychain Access.
    open /Applications/Utilities/Keychain\ Access.app
  2. Click on System Roots from the left Keychains sidebar.
  3. Typestartcom in the search bar.
  4. Select all the root certificates and press โŒ˜i.
  5. Expand the Trust section
    and change the option
    When using this certificate
    to
    Never Trust.

Put -L in your ssh config

If youโ€™ve used ssh youโ€™ve probably had to use local port forwarding before. If you use complex flags often, you will find remembering what you need is tiresome. Stop. Write it down so you can forget all about it. Save your memory.

Instead of doing this:

ssh -L 3000:localhost:3002 not_dillon@example.com

Add those flags to the ~/.ssh/config file instead:

# /home/dillon/.ssh/config

Host example
  User         not_dillon
  Hostname     example.com
  LocalForward 3000 localhost:3002

From now on, all you need to type is:

ssh example

The Case of the Default ๐Ÿ•ต

In Appleโ€™s Swift language switch statements must be what apple calls โ€œexhaustiveโ€. Iโ€™ve felt the term to be very literal. Literally exhaustive?

Example that does not work:

let count = 42

switch count {
case 1:
  print(1)
case 7:
  print(7)
}

The above statement does not work because itโ€™s missing a default case. Why? What if I donโ€™t want to do anything else? Why do I need to write something that wonโ€™t be used? Donโ€™t worry, there is an amazing and less โ€œexhaustiveโ€ way to handle these situations; simply default: ()

Correct example:

let count = 42

switch count {
case 1:
  print(1)
case 7:
  print(7)
default: ()
}

-y apt-get?

When installing packages on Ubuntu, you may find it really tiring to constantly confirm โ€˜yesโ€™ all the time. I know I did. And when it comes to scripting your installsโ€ฆ that really becomes a nuisance. Today I learned that apt-get has a -y flag:

Automatic yes to prompts;
assume "yes" as answer to all prompts and run non-interactively.

Rails Sandbox ๐Ÿ–

When you are working with a complicated database structure, and find yourself needing to debug a complex or dangerous (delete) action, you might be hesitant to experiment. Keep experimenting! Donโ€™t want to setup all that data again? No worries. You can use a sandbox $ rails c -s

Usage: rails console [environment] [options]
  -s, --sandbox      Rollback database modifications on exit.

The sandbox flag will keep all database changes in a database transaction when you start the rails console and automatically issue a rollback when you quit the console.