Today I Learned

A Hashrocket project

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

42 posts by andrewvogel @calllmehvogel

Get the Values for a Ecto Schema Enum Column

I recently started learning Elixir and had a model with an enum column with the following attributes:

 schema "keyboards" do
   field :nickname, :string
   field :form_factor, Ecto.Enum, values: [:macro, :num, :custom, :split, :forty, :sixty, :sixty_five, :seventy_five, :tkl, :full]

   timestamps()
 end

In my view, I had a form object where I wanted to have a select input with the values from my enum column, form_factor. Luckily, the Ecto.Enum module has a few functions that can help with this - mappings/2, values/2, and dump_values/2.

I ended up using the following in my form:

  <%= label f, :form_factor %>
  <%= select f, :form_factor, Ecto.Enum.mappings(Keyboard, :form_factor)  %>
  <%= error_tag f, :form_factor %>

https://hexdocs.pm/ecto/Ecto.Enum.html

Dynamically Render Client Side with Next.js

Next.js has a handy feature for rendering a child component client side, instead of on the server.

Consider the following example, where you have a component in src/components/Analytics.js with a default export.

import dynamic from "next/dynamic";

const DynamicAnalytics = dynamic(() => import("../components/Analytics"));

function Header(props) {
  return (
    <>
     <DynamicAnalytics />
     <OtherServerRenderedStuff {...props} />
    </>
  )
}

Named Exports

You can also use this dynamic importing feature with named exports. Consider the next example:

// src/components/Analytics.js
export function Analytics() {
  return (
    // ....
  )
}
import dynamic from "next/dynamic";

const DynamicAnalytics = dynamic(
  () => import("../components/Analytics").then(mod => mod.Analytics)
);

function Header(props) {
  return (
    <>
     <DynamicAnalytics />
     <OtherServerRenderedStuff {...props} />
    </>
  )
}

There are some gotcha’s when using dynamic, so make sure to check out the docs and get familiar with the API.
https://nextjs.org/docs/advanced-features/dynamic-import

Parse a Query String in Ruby

If you ever need to parse a query string in Ruby - or Rails, Rack has a convenient utility to do just that. parse_nested_query will parse from a string to a hash:

Rack::Utils.parse_nested_query("&sort_dir=asc&sort_by=date_created&filter_by=lead")

=>  {"sort_dir"=>"asc", "sort_by"=>"date_created", "filter_by"=>"lead"}

You can also go the opposite way with build_nested_query and generate a query string:

Rack::Utils.build_nested_query({"sort_dir"=>"asc", "sort_by"=>"date_created", "filter_by"=>"lead"})

=>  "sort_dir=asc&sort_by=date_created&filter_by=lead"

https://www.rubydoc.info/gems/rack/Rack/Utils

Use the <datalist> element for input suggestions

HTML5 has a <datalist> element which you can you use to create suggestions for inputs.

Just specify the list attribute on your input, whose value should correspond to the id of the datalist, and then populate your datalist with options:

<input list="programming-languages" id="programming-language-choice" name="programming-language-choice" />

<datalist id="programming-languages">
    <option value="Ruby">
    <option value="Elixir">
    <option value="Java">
</datalist>

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist

Conditionally Render Rails Links with `link_to_if`

ActionView::Helpers contains some really handy helpers for conditionally rendering links. For example, the link_to_if method will render the link if the given condition is met; otherwise it renders just the text.

<%= link_to_if(current_user.present?, "Admin Panel Tools", admin_tools_path) %>

When current_user.present? is true, yields the following HTML:

<a href="/admin_tools">Admin Panel Tools</a>

But when current_user.present? is false, yields the following:

Admin Panel Tools

https://api.rubyonrails.org/v6.0.2.1/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to_if

Note, there are also methods for link_to_unless and link_to_unless_current

Easily Format Phone Numbers in Rails

Included as part of ActiveSupport, Rails has a handy helper for formatting phone numbers. Passing a number/string to the helper and some optional args and it will take care of the rest.

Here’s a few examples of how it works:

number_to_phone(8858846) # 885-8846
number_to_phone(8778858846) # 877-885-8846
number_to_phone(8778858846, area_code: true) # (877) 885-8846

To see all the options for the method, check out the Rails docs https://api.rubyonrails.org/classes/ActiveSupport/NumberHelper.html#method-i-number_to_phone

What Is the Rails Notes Command?

While reading through the Rails 6 changelog, I noticed an entry for a rails command called notes. Having never seen this before, I took a quick look at the Rails Guides.

The command rails notes will return a list of all instances of the following annotations in your codebase - FIXME, OPTIMIZE, and TODO.

You can optionally search for your own custom annotations with the --annotations (-a) flag:

rails notes -a NOTE
app/controllers/admin/blog_posts_controller.rb:
  * [10] [NOTE] Only return the last 10 blog posts

README.md:
  * [ 1] [NOTE] Set the following env variables

There’s also a way to register your own custom annotations for use with the default runner

config.annotations.register_tags("DEPRECATEME", "TESTME")

Rails Guides - Rails Notes

Disallow Large Jest Snapshots Using ESLint

eslint-plugin-jest has a handy rule to limit the size of snapshots - no-large-snapshots. This is especially useful for maintainability of snapshot tests. Snapshots of a large component are cumbersome to maintain as it requires the dev to have a deep knowledge of this components DOM and contained logic.

Github - eslint-plugin-jest/no-large-snapshots

Bonus Round: React Native has updated their documentation and added some great testing guidelines for mobile apps.

Show All Docker Containers (Running & Stopped)

docker ps was a confusing command for me, because I thought that it would show all containers by default. But trying to run a container that has been stopped will give you a an error that there is a container in use with that name. Running docker ps will be fruitless because that container will not be listed by default. You can easily view all your containers, running & stopped, with:

docker ps -a

H/T to the docs lol - https://docs.docker.com/engine/reference/commandline/ps/

Import GraphQL Queries Where You Need Them (CRA)

If you want to export/import your GraphQL queries in your Create-React-App, you’ll need to add a library to make it work. You can use a library called grapql.macro to use .gql or .graphql files in your app.

If you’re also using jest, you’ll need to add jest-transform-graphql and update your jest configuration to pick up these file types.

Source: https://www.apollographql.com/docs/react/integrations/webpack/#jest

Use `reset_column_information` to Migrate Data

If you’re generating a Rails migration, chances are you might need to facilitate migrating data to a new column. You can use reset_column_information in a migration file to pick up your changes and immediately do something with those new columns.

Assuming we have 2 models, DraftPost and Post -

  class AddColumnDraftToPosts < ActiveRecord::Migration[5.2]
    def change
      add_column :posts, :draft, :boolean, default: true
      Post.reset_column_information
      DraftPost.all.each do |draft_post|
        Post.create(content: draftPost.content)
        draft_post.delete
    end
  end

Define Your Refs with React ElementRef Flow Type

If you’re using flow in your React Native project, chances are that you are probably using refs. In this case, you’ll need to define your ref types.

Let’s say we have ref on a FlatList component from React Native:

import React from 'react'
import { FlatList } from 'react-native'
import data from './data.json'

class MyList extends React.Component {
  renderItem = () => {
    // rendering stuff
  }
  render() {
    return(
      <FlatList
        ref={l => (this.l = l)}
        data={data}
        renderItem={this.renderItem}
      />)
  }
}

Instead of being lazy and just using any, we’ll use the React.ElementRef flow type. It takes an additional typeof argument, which is the component type of your ref.

// @flow
import * as React from 'react'
import { FlatList } from 'react-native'
import data from './data.json'

class MyList extends React.Component<{}, {}> {
  l: ?React.ElementRef<typeof FlatList>
  renderItem = () => {
    // rendering stuff
  }
  render() {
    return(
      <FlatList
        ref={l => (this.l = l)}
        data={data}
        renderItem={this.renderItem}
      />)
  }
}

Node Version - 10.14.1 Flow-Bin Version - 0.107.0

Flow docs - ref functions

Edited: 9/11/2019 - Fix fat arrow, Add typing to MyList, Update description to React Native, Resolve syntax error for ref typing

Fix Timeouts and Speed Up Jest on CI

If you have a large or complex test suite, running Jest on CI can make your build especially slow or cause timeouts. Luckily, Jest has an option cut out for this.

jest --runInBand

runInBand runs all tests serially inside the current process instead of creating a bunch workers. Using this option, we were able to trim suite time above 20mins down to less than 4 mins. 🏎️😎

Change Name of :id Param in Rails Resource Routing

You can change the name of the parameter used by rails resource routing by specifying the param option to the resource route.

For example, if we have a show endpoint:

resources :pages, only: :show

Running rake routes will return the pages#show url as:

/pages/:id

We can change the id param to slug by doing:

resources :pages, only: :show, param: :slug

Then running rake routes will yield our new pages#show route as:

/pages/:slug

Generate Absolute File Path In Ruby

Pass a string to File.expand_path to generate the path to that file or directory. Relative paths will reference your current working directory, and paths prepended with ~ will use the owner’s home directory.

File.expand_path('example.rb')
=> "/Users/avogel/My/Working/Directory/example.rb"

File.expand_path('~/example.rb')
=> "/Users/avogel/example.rb"

Deleting Words in Vim

As a recent vim convert, I learn new commands everyday from my fellow rocketeers. Here’s some cool ones for deleting words:

  • Use dw to delete word. Cursor placement is important! If your cursor is not on the first character, it will only delete from your cursor to the end of the word.
  • Use diw to delete inside word. Deletes the entire word that your cursor resides in.
  • Use dt<char> to delete to character. Deletes from your cursor to the specified character.

Getting BetterErrors in Rails While Using Ngrok

By default, the BetterErrors gem only works for localhost. If you’re using ngrok to access your rails server and you want to have access to BetterErrors, you’ll need to whitelist the IP of the machine that ngrok is running on.

Add the following to development.rb:

BetterErrors::Middleware.allow_ip!(NGROK_MACHINE_PUBLIC_IP)
# - or -
# Use an IP finding service to grab your public IP each time you start the server:
# (ipecho.net, api.ipify.org, etc.)
BetterErrors::Middleware.allow_ip!(open('http://api.ipify.org').read)

Set the Domain or Host in Rails URL Helpers

You can explicitly set the domain with Rails URL Helpers:

app.root_url
=> "http://www.example.com/"

app.root_url(domain: 'www.customdomain.org')
=> "http://www.customdomain.org"

 

If we include the protocol in our domain argument, this brings up an interesting gotcha. Check it out:

app.root_url(domain: 'http://www.customdomain.org')
=> "http://www.http://www.customdomain.org/"

 

Thankfully, Rails gives us the host option. Host will use a Regex to remove the protocol from the argument:

app.root_url(host: 'https://www.customdomain.org')
=> "http://www.customdomain.org/"

 

Notice the output in the last example; our protocol was replaced removed and replaced with http. You can use the protocol argument to enforce your protocol on a given host:

app.root_url(host: 'http://www.customdomain.org', protocol: 'https')
=> "https://www.customdomain.org/"

Alias A Model Method Or Field in a GraphQL Type

If you need to override the name of a model method or field in a GraphQL type, you can use the property argument.

Let’s say we have a model called Message:

class Message < ApplicationRecord
  def bar
    "bar"
  end
end

We can then make an alias to the bar method by specifying a property in our MessageType:

Types::MessageType = GraphQL::ObjectType.define do 
  field :foo, types.String, property: :bar
end