Today I Learned

hashrocket A Hashrocket project

Shorthand Elixir Anonymous Functions

Elixir has a really cool syntax for writing anonymous functions (unnamed functions). It goes like this:

# The '&' operator is used to define the function and its arguments
putter = &(IO.puts &1)
putter.("Today I learned")
#...> Today I learned
#...> :ok

&1 refers to the functions first argument; in this example, we used “Today I learned". You can use more arguments with &2, &3, etc… For example:

combiner = &(&1 <> &2)
combiner.("Foo", "Bar")
#...> "FooBar"

Also notice that we are not calling either of these functions like function(arg1, arg2), instead we are calling them like function.(arg1, arg2). We have to use . because we are not actually naming these functions, we are only assigning them a reference, hence “anonymous" functions.

Rails' .insert_all method is too naive

Rails requires a unique index in order to use the .insert_all methods. This requirement can make this method very brittle and unusable. If your conflict target is the table’s primary key, this won’t work unless you create a redundant index on the table for this method to match against. This creates an amazing amount of waste not only of storage space, but also performance. This method would allow so many more use cases if it simply let you describe the conflict you want to match against.

More advanced method:

class ApplicationRecord
  def self.bulk_insert(array_of_hashes, conflict_targets = Array(primary_key))
    columns = array_of_hashes.first.keys
    values = array_of_hashes.flat_map(&:values)
    rows = array_of_hashes.map do |f|
      "(#{columns.size.times.map { "?" }.join(", ")})"
    end.join(", ")

    sql = sanitize_sql_array([<<~SQL, *values])
      INSERT INTO "#{table_name}"
      (#{columns.map { |c| "\"#{c}\"" }.join(",")})
      VALUES #{rows}
      ON CONFLICT (#{conflict_targets.map { |c| "\"#{c}\"" }.join(", ")}) DO NOTHING
    SQL

    connection.execute(sql.squish)
  end
end

SQL it produces:

User.bulk_insert([{email: "a@example.com"}, {email: "b@example.com"}])
INSERT INTO "users" ("email") VALUES ('a@example.com'), ('b@example.com') ON CONFLICT ("id") DO NOTHING

This would then allow you to reference any conflict you like:

alter table users add unique (email);
User.bulk_insert(
  [{email: "a@example.com"}, {email: "b@example.com"}],
  %i[email]
)

The difference between %w and %W in Ruby

%w can construct space delimited word arrays like this

%w(my cool word array)
#=> ["my", "cool", "word", "array"]

%W works similarly, however it offers ways to interpolate with variables and escape special characters in the assignment, while %w does not.

street_name = 'Sesame Street'
%W(I live on #{street_name})
#=> ["I", "live", "on", "Sesame Street"]

Sort array of numbers

In javascript, the Array sort function will cast everything to a string. So when you have an array of numbers, you need to repetitively cast them to numbers:

[1080, 720, 480].sort()
=> [1080, 480, 720]

So you need have to write your own sort function (don’t mess it up!)

[1080, 720, 480].sort((a, b) => Number(a) - Number(b))
=> [480, 720, 1080]




🐉

Limit Jest Test Coverage to Specific Files

Jest allows you to view test coverage when it runs.

But even if I only run tests for a specified file or files, the --coverage output will include all files.

$ jest src/directoryToTest --coverage

If your app is large, this can generate a lot of output and be difficult to parse. If I only care about the coverage for files in directoryToTest, I can filter the output of --coverage with --collectCoverageFrom=:

$ jest src/directoryToTest --coverage --collectCoverageFrom="src/directoryToTest/**"

docs

SVG using href must be served from same site url

We like to use SVG sprites

However, if you utilize a cdn or serve assets from an external source… you may run into some issues. Hopefully you find this and it saves you time that we lost figuring this out

According to MDN:

For security reasons, browsers may apply the same-origin policy on use elements and may refuse to load a cross-origin URL in the href attribute. There is currently no defined way to set a cross-origin policy for use elements.

Remove Padding from Postgres Formatting Functions

Earlier today, I was trying to join a table on a formatted string row, but wasn’t getting the results I expected. Turns out that my formatting string had blank padding and I discovered “fill mode".

When using postgres formatting functions, like to_char, some of the formatting options include padding in the result. For example, the day format string will be blank padded to 9 chars.

select to_char(current_date, 'day');

  to_char
-----------
 sunday

You can use the “fill mode" (FM) option to remove any leading zeros or blank padding characters by prepending it to your format option:

select to_char(current_date, 'FMday')

 to_char
---------
 sunday

https://www.postgresql.org/docs/current/functions-formatting.html

Find Unused Cucumber Step Definitions

One of the challenges of using cucumber is properly managing your step definitions. Left unchecked, you will eventually have many unused steps. It’s extremely cumbersome to prune these manually. Luckily, you can use cucumber’s -f / --format flag to get feedback on unused step_definitions and their locations:

bundle exec cucumber --dry-run --format=stepdefs

If your step definition is unused, it will be annotated with a line under that says NOT MATCHED BY ANY STEPS. See the example -

/^I submit the proposal request form$/     # features/step_definitions/contact_steps.rb:39
  NOT MATCHED BY ANY STEPS

Parsing nested string json

Today I learned how to parse a nested json with jq, but the nested json is a string. It’s just easier to show an example so here we are:

{
  "a": "{\"b\": \"c\"}"
}

This is not a common situation but I found that out today on a codebase and my first thought was to call jq get the content of the node a and then pipe it into another jq command. It would look like this:

echo '{"a": "{\\"b\\": \\"c\\"}"}' | jq '.a' | jq
# => "{\"b\": \"c\"}"

As we can see the result is not a json, but a string so we cannot access inner nodes just yet.

And the solution to this problem is to use the -r flag on the first jq call to output the result in a raw format, so the " surounding double quotes will disappear. And with that in place we can easily parse the nested/nasty json:

echo '{"a": "{\\"b\\": \\"c\\"}"}' | jq -r '.a' | jq
# => {
# =>   "b": "c"
# => }

Then finally:

echo '{"a": "{\\"b\\": \\"c\\"}"}' | jq -r '.a' | jq '.b'
# => "c"

Direct many-to-many ActiveRecord associations

By using has_and_belongs_to_many you can directly relate models with a many-to-many-association. For example, if you have a model Film and a model Producer, a film can have multiple :producers, and a Producer can have multiple :films; in this case, each of models could have a has_and_belongs_to_many association with the other.

#film.rb
class Film < ApplicationRecord
  has_and_belongs_to_many :producers
end

#producer.rb
class Producer < ApplicationRecord
  has_and_belongs_to_many :films
end
#Now associative records can be stored and retrieved
film = Film.where(title: "The Irishman")
producer = Producer.where(name: "Martin Scorcese")
film.producers << producer
film.save

producer.films
#=> This should return a collection including The Irishman
films.producers
#=> This should return a collection including Martin Scorcese

Confirmation alert using Turbo in Rails

Using an confirmation browser alert is a common thing to do. This seemingly works fine while using Turbo, however, try to cancel the alert and your request still goes through.

Previously on rails:

<%= link_to "Go away", "/go-away", data: {confirm: "Are you sure?"} %>

Use data-turbo-confirm for correct functionality:

<%= link_to "Go away", "/go-away", data: {turbo_confirm: "Are you sure?"} %>

Pattern Match Keyword List in Function Def

TIL that you can pattern match a keyword list in a function definition.

Sometimes you’ll receive the last argument as an empty keyword list; in this case, we’re calling it opts. You can pattern match key-values by defining the shape in a function.

  defmodule Example do
    def hello(opts \\ [])
 
   # will match when message is the only keyword included in opts
    def hello([message: message]) do
      IO.puts("Hello #{message}")
    end

    # will match when there are multiple keywords but message is the first
    def hello([{:message, message} | _rest] = opts) do
      IO.puts("Hello #{message}")
    end
  end

> Example.hello(message: "World")
Hello World
:ok

# could also call in the more verbose way
> Example.hello([message: "World"])
Hello World
:ok

# :message but with other args after
> Example.hello(message: "World", something_else: "hi")
Hello World
:ok

Note: As a TIL reader pointed out, pattern matching Keywords will make your function args order dependent. The following would not work:

> Example.hello(foo: "bar", message: "World")
** (FunctionClauseError) no function clause matching in Example.hello/1 

If you need them to be order independent, use a map or just match on single argument, then check for each option appropriately with the Keyword module.

https://elixir-lang.org/getting-started/keywords-and-maps.html

Creating Custom Typescript Types

In TypeScript you can build your own custom object types. Custom types work just like any other type. You can use it like this:

type Vehicle = {
    make: string, 
    model: string, 
    capacity: number, 
}

//now we can use the vehicle type in a definition
const corolla: Vehicle = {
    make: "Toyota",
    model: "Corolla", 
    capacity: 5,
}

If you define a vehicle without any of the required types, TypeScript will provide an error stating which property is missing from the object. For example:

const corolla: Vehicle = {
    make: "Subaru",
    model: "Outback", 
}

This definition will provide an error Property 'capacity' is missing in type '{ make: string; model: string; }' but required in type 'Vehicle'., because the capacity property is missing.

Ruby memoization with nil values

As Ruby developers, we’re often looking for ways to reduce time consuming lookups in our code. A lot of times, that leads us to memoizing those lookups with the common ||= operator.

However, if our lookups return a nil or falsey value, our memo will actually keep executing the lookup:

def ticket
  @ticket ||= Ticket.find_by(owner:)
end

This code essentially boils down to:

def ticket
  @ticket = @ticket || Ticket.find_by(owner:)
end

If our find_by in the example above returns nil, the code will continue to run the find_by every time we call the ticket method.

To avoid this, we can shift our pattern a bit, and look to see if we have already set our instance variable or not:

def ticket
  return @ticket if defined?(@ticket)
  @ticket = Ticket.find_by(owner:)
end

The Outlet of Rails Stimulus

Besides values and targets Rails Stimulus has now outlets. Now we can invoke functions from one controller to the other like that:

// result_controller.js
export default class extends Controller {
  markAsSelected(event) {
    // ...
  }
}
// search_controller.js
export default class extends Controller {
  static outlets = [ "result" ]

  selectAll(event) {
    this.resultOutlets.forEach(result => result.markAsSelected(event))
  }

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);
}