Today I Learned

hashrocket A Hashrocket project

Encrypting database columns with Rails 7

In Rails 7, Active Record includes the option to encrypt database columns. To start run bin/rails db:encryption:init, then copy the resulting keys to your app’s credentials.yml. Now in your model, you can tell Active Record to encrypt a column by using the encrypts which takes a db column name as an argument. For example:

class User < ApplicationRecord
  encrypts :super_secret_data
end

Active record will automatically decrypt the data upon retrieval. See more here.

!important property

If you ever find yourself with conflicting rules on an element, and want one to take precedence even over rules with higher specificity. you can add the !important property, to override all other rules.

Here is an example:

#id {
  background-color: blue;
}

.class {
  background-color: gray;
}

p {
  background-color: red !important;
}
<p>This is some text in a paragraph.</p>

<p class="class">This is some text in a paragraph.</p>

<p id="id">This is some text in a paragraph.</p>

In this case, all of these <p> tags will have a red background.

Javascript private class functions

Today I learned that you can make a class function to be private by prefixing it with a #. Check this out:

class MyClass {
  myPublicFunc() {
    console.log("=> hey myPublicFunc");
    this.#myPrivateFunc();
  }

  #myPrivateFunc() {
    console.log("=> hey myPrivateFunc");
  }
}

const myClass = new MyClass();

myClass.myPublicFunc();
// => hey myPublicFunc
// => hey myPrivateFunc

myClass.#myPrivateFunc();
// Uncaught SyntaxError:
//   Private field '#myPrivateFunc' must be declared in an enclosing class

We can also use that trick to make static methods, fields or static fields as well.

Thanks Andrew Vogel for this tip 😁

Composite Primary Keys using Elixir Ecto

Ecto allows us to map a table with composite primary keys into our schemas. The way to do that is by using the primary_key: true option for the Ecto.Schema.field/3 instead of dealing with the special module attr @primary_key. Check this out:

defmodule MyApp.OrderItem do
  use Ecto.Schema

  @primary_key false
  schema "order_items" do
    field :product_id, :integer, primary_key: true
    field :order_id, :integer, primary_key: true
    field :quantity, :integer
  end
end

How to Change Password of SSH key

It’s possible to change the password of your current ssh key if you have the current password or it is not currently password protected. You can use the command:

ssh-keygen -p

From the man pages -

Requests changing the passphrase of a private key file instead of
creating a new private key.  The program will prompt for the file
containing the private key, for the old passphrase, and twice for
the new passphrase.

https://man.openbsd.org/ssh-keygen.1#p

Identity Primary Keys on Ecto

Ecto, allow the type :identity to be used since 3.5 which is cool:

create table(:user, primary_key: false) do
  add :id, :identity, primary_key: true
  add :name, :string, null: false

  timestamps()
end

That generates this SQL:

CREATE TABLE public.users (
    id bigint NOT NULL,
    name character varying(255) NOT NULL,
    inserted_at timestamp(0) without time zone NOT NULL,
    updated_at timestamp(0) without time zone NOT NULL
);

ALTER TABLE public.users ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
    SEQUENCE NAME public.users_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1
);

The only issue is that there’s no option to change from BY DEFAULT to ALWAYS 😕

ActiveStorage direct upload subfolders

When using S3, Rails does not let you configure subfolders for active storage. Every attachment lives at the root of your bucket.

This is not recommended, but cannot be helped

The only way to store attachments into subfolders is to monkey patch the direct uploads controller:

# config/initializer/direct_uploads_monkey_path.rb
Rails.application.config.to_prepare do
  class ActiveStorage::DirectUploadsController < ActiveStorage::BaseController
    def create
      key = "#{sub_dir}/#{user.id}/#{ActiveStorage::Blob.generate_unique_secure_token}"
      upload_attrs = {key:}.merge(blob_args)
      blob = ActiveStorage::Blob.create_before_direct_upload!(**upload_attrs)
      render json: direct_upload_json(blob)
    end

    private

    def user
      @user ||= User.enabled.find(session[:current_user_id])
    end

    def sub_dir
      if /cool-uploads/.match?(request.referer)
        "cool-uploads"
      elsif /company-settings/.match?(request.referer)
        "companies"
      else
        "uploads"
      end
    end
  end
end

Phoenix component attr definition

The new Phoenix.Componentattr/3 function is awesome. It does compile time validations to all the places that are calling the component and also it comes with nice and useful options. For instance the default option:

attr :name, :string, default: "there"

def greet(assigns) do
  ~H"""
  

Hey <%= @name %>!

""" end

That’s very useful. That would totally replace most usages of assign_new like I used to do:

def greet(assigns) do
  assigns = assign_new(assigns, :name, fn -> "there" end)

  ~H"""
  

Hey <%= @name %>!

""" end

This way we can call:

<.greet />

And have this html generated:

Hey there!

Group Json data by a key

Today I learned how to group by json data by a key using jq. In ruby that’s very trivial, it’s just about using the group_by method like that:

[
  {name: "John", age: 35},
  {name: "Bob", age: 40},
  {name: "Wally", age: 35}
].group_by{|u| u[:age]}

# {
#   35=>[{:name=>"John", :age=>35}, {:name=>"Wally", :age=>35}],
#   40=>[{:name=>"Bob", :age=>40}]
# }

But using jq I had to break it down to a few steps. So let’s say that I have this json:

[
  {"name": "John", "age": 35},
  {"name": "Bob", "age": 40},
  {"name": "Wally", "age": 35}
]

The idea is that we’ll call the group_by(.age)[] function to return multiple groups, then I pipe it to create a map with the age as the key. Finally we’ll have these bunch of nodes not surounded by an array yet, so I am pipeing to a new jq command to add with slurp:

cat data.json |
  jq 'group_by(.age)[] | {(.[0].age | tostring): [.[] | .]}' |
  jq -s add;

# {
#   "35": [{"name": "John", "age": 35},{"name": "Wally", "age": 35}],
#   "40": [{"name": "Bob", "age": 40}]
# }

Quickly find module inclusion in Ruby

Given I have a class like so:

class Location < ActiveEnum::Base
  include WithLabel
end

Normally I would check for inclusion of something via a declarative method like:

Location.ancestors.include?(ActiveEnum::Base)
=> true

Location.ancestors.include?(String)
=> false

Location.included_modules.include?(WithLabel)
=> true

However it never occured to me that < is defined on Class and returns true if is a subclass of the requested module.

So we can do something like this now:

Location < ActiveEnum::Base
=> true

Location < String
=> nil

Location < WithLabel
=> true

Subtle difference is that the ‘falsey’ case returns nil instead of false.

Also the definition of this method states that it returns true if module is a subclass of other but I’ve found that it returns true for methods that are included as well. Take that as you will.

Select options with option groups

ActionView::Helpers::FormOptionsHelper#grouped_options_for_select lets you pass in a nested array of strings, and returns a string of option tags wrapped with optgroup tags:

The first value serves as the optgroup label while the second value must be an array of options.

grouped_options = [
  ["North America",
    ["United States", "Canada"]],
  ["Europe",
    ["Denmark", "Germany", "France"]]
]

Next, simply call it from a form helper in the view, passing in your grouped options.

<%= f.select :location, grouped_options_for_select(grouped_options) %>

Voila! You should now have something like this: image

Execute CDP from a Capybara test

Selenium Chrome:

With capybara you can access the page’s driver by using page.driver. Next you can access the browser methods on the driver withpage.driver.browser, then the .execute_cdpmethod can be used to execute chrome devtools commands on your webdriver browser.

In a capybara test, it could like:

test_browser = page.driver.browser

coordinates = { latitude: 35.689487,
                longitude: 139.691706,
                accuracy: 100 }

test_browser.execute_cdp('Emulation.setGeolocationOverride', **coordinates)

This can be used to mock some client-side information such as user device metrics, geo-location, or even emulate slow CPUs 😳

Upgrade Heroku PostgreSQL

We just did a PostgreSQL bump in Heroku from 13.8 => 14.5 (the latest Heroku supports at this day). The process was very smooth and kind of quick for a 1GB database. Here’s the script we end up running:

# Change the following `basic` to the right plan for you
heroku addons:create heroku-postgresql:basic -r heroku-staging
heroku pg:wait -r heroku-staging
heroku pg:info -r heroku-staging
# Now grab the NEW and OLD URLS to change the following commands:

heroku maintenance:on -r heroku-staging
# It took less than 2 mins for a 1GB database
heroku pg:copy DATABASE_URL CHANGE_HERE_NEWCOLOR_URL -r heroku-staging
# It's usually fast, it depends on how long the app takes to reboot
heroku pg:promote CHANGE_HERE_NEWCOLOR_URL -r heroku-staging
heroku maintenance:off -r heroku-staging

heroku addons:destroy CHANGE_HERE_OLDCOLOR_URL -r heroku-staging

Error Handling in Typescript

Exceptions caught in a try/catch are not guaranteed to be an instance of Error class, or a child of Error . The exception caught can be anything - an object of any type, string, number, null… you name it.

So in typescript, the compiler won’t like:

try {
  //...
} catch(e) {
  console.log(e.message); // => Compiler will error with `Object is of type 'unknown`
}

The typescript compiler can’t infer the type of e, so defaults the type to unknown. If you know your error will have a message, you can do something like this:

try {
  //...
} catch(e) {
  const error = e as { message: string };
  console.log(error.message);
}

Lock Device Screen Orientation with JavaScript

The window object has a great API for working with screens(mobile devices, etc) and their related metadata - window.screen and window.screen.orientation.

For Mobile Devices and Full Screen browsers, you can use the following methods to toggle orientation locks:

window.screen.orientation.lock("portrait-primary")

window.screen.orientation.unlock()

Note that when you call the lock API on a web browser that is not full screen, it will raise a DOMException similar to following:

DOMException: screen.orientation.lock() is not available on this device.

Acceptable orientation values can be found in the docs linked below

https://developer.mozilla.org/en-US/docs/Web/API/ScreenOrientation/lock

Chrome has a command palette

I had no idea that Google Chrome had a command palette!

Once your dev tools are open, you can activate through the interface by clicking the ‘more’ icon and then ‘Run command’ like so:

or by cmd+shift+p (on a mac). Coincidentally this is the same keyboard shortcut as the command palette in Visual Studio Code

Now you’ll see the palette which gives you direct access to a lot of Chrome’s hidden functionality!

image

Truncate by Word Count in Rails

Rails has a convenient method for truncating strings based on word count.

my_string = "Hello World you are now reading a til post"
my_string.truncate_words(2) 
#=> "Hello World..."

The method automatically adds ... to the end of the string to indicate that the string has been shortened. You can customize this omission by passing an omission argument.

my_string.truncate_words(2, omission: "... (read more)")
#=> "Hello World... (read more)"

Add custom flash keys

ActionController::Base has a default flash types of [:notice] allowing you to pass a key to #redirect_to:

class Controller < ActionController::Base
  def create
    if true
      redirect_to root_path, status: :see_other, notice: "Created"
    else
      flash[:error] = "Could not create"
      redirect_to root_path, status: :see_other
    end
  end

However, you can add :error as a custom type to get the convenience argument:

class Controller < ActionController::Base
  add_flash_types :error

  def create
    if true
      redirect_to root_path, status: :see_other, notice: "Created"
    else
      redirect_to root_path, status: :see_other, error: "Could not create"
    end
  end

Attaching Fixture Files in Rails Model Tests

If you want to attach fixture files in a model test. Assuming your ActiveStorage association is already set up, if it’s not set up, check this out, then follow these steps:

  1. Make sure your desired fixture file has been placed in your test/fixtures/files folder
  2. Attach the fixture to the model instance by providing the .attach method with a hash including an IO object and the name of the file you wish to attach.

It should look something like this:

@object.image.attach(io: File.open('test/fixtures/files/filename'), filename: 'filename')

Enforce TLS... except for health checks

Many infrastructure stability platforms will need to check the health of a rails application directly, not through a load balancer. Because many applications don’t terminate TLS directly (because it’s delegated to the load balancer) a health check endpoint must adhere the the force_ssl = true config option, but without TLS, causing a failure.

Rails 7 has an option to work around this (config.ssl_options):

# config/environments/production.rb
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = true
config.ssl_options = {
  redirect: {exclude: ->(request) { /healthz/.match?(request.path) }}
}

Limiting object counts in rails associations

Let’s say you have a model owner and a model pet. Every owner has_many pets, but you want to limit the number of pets an owner can have. You can use the following validation in your model to make sure these owners don’t get greedy with their number of pets.

has_many :pets
validates :pets, length: { maximum: 5 } 

The length helper is telling rails to only allow an owner to have a maximum of 5 pets. This is a little awkward because the length helper usually pertains to enforcing a minimum or maximum length on a string attribute, but it still works on the ActiveRecord Association Collection of :pets in a similar way where it is basically validating the maximum size of the collection.

Endless Range

If you have a Range that you want to extend infinitely in either direction, just simply leave it blank.

Here’s a simple example:

def age_category(age)
  case age
  when (...0)
    "unborn"
  when (0..12)
    "youngling"
  when (13..17)
    "teenager"
  when (18..64)
    "adult"
  when (65..)
    "old"
  end
end
>> age_category    0 => "youngling"
>> age_category   13 => "teenager"
>> age_category   18 => "adult"
>> age_category   65 => "old"
>> age_category  999 => "old"
>> age_category -999 => "unborn"

In a situation like this it’s nice to extend infinitely, rather than having to come up with some kind of arbitrary cutoff age like 100, that could in rare cases cause problems.

H/T Matt Polito for showing me this.

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.

Rails TimeHelpers

Rails ActiveSupport testing library includes some really helpful methods for manipulating time.

Here’s a cool one:

travel_to Time.new(2022, 9, 14) do  
    #everything inside this block is now happening as if it is 9-14-2022

end
# afterwards we return to the present

This way you can test all sorts of time-based features by jumping back and forth through time.

Trippy 🔮

Nil is actually NilClass

If for whatever reason you wanted to modify something inside nil, the class name is NilClass

🤯 Yes it’s very mind blowing, I know 🤯

Example of bad code I couldn’t figure out why it wasn’t working

class Nil
  def to_i
    -1
  end
end

The fix needed to make it behave as expected

class NilClass
  def to_i
    -1
  end
end

CSS Filter Property

There is a CSS property filter which allows you to manipulate properties of an element such as their blur, brightness, contrast, grayscale, opacity, and more. This is most often applied to images, but does work on any element.

.single-filter {
  filter: opacity(50%);
}
.multiple-filters {
  filter: blur(5px) grayscale(100%);
}

Here comes our friendly neighborhood Pikachu to help show us what this looks like: image

Here is a good reference for what filter can do.

Duplicating tabs in iTerm

If you want to open a new tab in iTerm in your same working directory, you can use the following steps:

  1. Navigate to preferences or use (⌘,).
  2. Click on the Keys settings.
  3. Hit + in the bottom left corner to add a new shortcut.
  4. Record a shortcut of your choosing
  5. Using the dropdown menu for Action: select ‘Duplicate Tab"

Now your new shortcut will open a new iTerm tab in whatever directory you have currently open.

Rename Git Remote

If the need arises to change the name of a git remote, in the past, I’ve normally done one of these two things:

  • delete and recreate the remote
  • manually edit the remote info in .git/config

Turns out there is a git command for this and I completely missed it!

git remote rename OLD_NAME NEW_NAME

As you can probably imagine, this handles the name change as well as the reference change in the config.

Clone a specific git branch

After cloning a repo, git will set your branch to whatever the value of HEAD is in the source repo:

$ ssh git@example.com
$ cat my-repo.git/HEAD
ref: refs/heads/master

If there is a different branch you want to clone, use the --branch flag:

$ git clone --branch my-feature git@example.com/my-repo.git
$ git branch
* my-feature