Today Icelab Learned

NGINX proxy_pass DNS caching and Heroku

We occasionally use NGINX to proxy_pass to an app on Heroku so that we have a consistent IP address that can be whitelisted in clients’ firewalls. There’s a quirk in the way that NGINX caches DNS lookups that can cause problems when proxying to Heroku.

By default, NGINX will cache the DNS result (the resolved IP address) for any statically defined URLs for proxy_pass on startup. This is problematic on Heroku because the IP address for the underlying app changes continuously, and so it a cached result falls out of Heroku’s routing mesh we can proxy to the incorrect server.

The solution to this problem is to avoid caching the DNS resolution, at least as much as is possible. You can “trick” NGINX into doing this by setting the proxy_pass value to a variable:

server {
  resolver 8.8.8.8;
  # Set as a variable to force dynamic lookups
  set $proxy_pass_url https://my-app.herokuapp.com;

  location / {
    proxy_pass $proxy_pass_url;
  }
}

Even though the value of the variable never changes, NGINX thinks it might and thus will refresh it regularly.

Kwarg splat arguments create new hashes

I made a method with a “double splat” kwarg argument to accept some options and returned a modified hash:

def singularize_options(**options)
  options = options.dup

  # mutate `options` here...

  options
end

In my usual “don’t mutate things we don’t own” style, I #duped the incoming options before going to work on it.

However, thanks to our dry-rb/rom-rb friend @flash-gordon, I learnt this wasn’t necessary! As he says:

when you capture values with **, Ruby creates a new hash instance so calling .dup is not needed

def foo(**options)
  options.object_id
end

{}.tap { |h| puts foo(h) == h.object_id }
# => false

Now my method can be even simpler:

def singularize_options(**options)
  # mutate `options` here...
  options
end

Rake tasks with splat arguments

Splat arguments can be helpful, and it turns out we don’t need to forsake them when we’re building Rake tasks, either.

Let’s say we want to build an elasticsearch:reindex task that allows an optional list of entity types to reindex:

namespace :elasticsearch do
  task :reindex do |_t, args|
    Search::Container["search.operations.enqueue_reindex"].(*args.extras)
  end
end

Rake gives us a TaskArguments#extras method, which returns the values not associated with any of the task’s named arguments.

So now we can call our rake task like so:

rake elasticsearch:reindex[products, posts]

And args.extras gives us this:

["products", "posts"]

background-position precision with the calc property

Positioning a background-image with CSS can be a frustrating experience. In fixed layout contexts you can use pixel or percentage values for precision positioning, but when you‘re working with a fluid layout, this can be a real headache.

Let‘s start by reviewing the background-position property:

.element {
  background-position: xpos ypos;
}

In most cases, this will be a fixed unit (20px 30px), a percentage (50% 100%), some keywords (left top) or a combination thereof (right 20%).

So what can you do if you want to position an image 40px from the right edge of your fluid-width container? Our favourite calc property to the rescue!

.element {
  /* Position the background at the right edge, then offset it by `40px` */
  background-position: calc(100% - 40px) top;
}

Restore a file with git checkout

If you commit and push a change to a file that you later discover you didn’t mean to make, you can restore a previous version of a file with git checkout

Let’s presume we have the following history:

*aacc272 - accidental change to foo.rb
*ac2eb0b - changes
*c688457 - bug fixes

We know that foo.rb is in the state we want in the commit with hash ac2eb0b. So we can run $ git checkout ac2eb0b path/to/foo.rb.

It’s worth noting that unlike passing a branch name to git checkout, this method is destructive and can discard changes in the working directory.

From the git man pages:

git checkout [-p|–patch] [] [–]
When or –patch are given, git checkout does not switch branches. It updates the named paths in the working tree from the index file or from a named (most often a commit). In this case, the -b and –track options are meaningless and giving either of them results in an error. The argument can be used to specify a specific tree-ish (i.e. commit, tag or tree) to update the index for the given paths before updating the working tree.

Using the flexbox order property to rearrange elements

Flexbox introduces a whole bunch of neat layout tricks that we can start using today. Things that we once needed JavaScript (or duplicate markup) to achieve are now doable with plain old CSS.

One such property that I’ve found myself using repeatedly is order. We can assign a numeric value to a set of elements and the browser will reorder those elements on the screen — without manipulating the DOM.

Adjust the order property in the following example to see the technique in effect:

The flexbox order property mAVYGy

So where is this useful? There’s a bunch of scenarios you might like to utilise this property: one example is bringing a navigation <aside> to the top of the screen on a mobile optimised layout.

If you resize your browser to a phone-width and inspect the source of icelab.com.au, you’ll see that the navigation bar in the footer exists below the contact detail elements in the DOM. But thanks to our friend order, we can completely rearrange the layout for this context. Neat!

Detect Google Analytics blocking

In a world filled with ad blockers, you can’t rely on Google Analytics dependencies being available when you want to use them. We hit upon this problem when doing some outbound link tracking like so:

function trackOutboundLink (e, url) {
  e.preventDefault()
  ga('send', 'event', 'outbound', 'click', url, {
    'transport': 'beacon',
    'hitCallback': function() {
      document.location = url
    }
  })
}

If Google Analytics is blocked, this function would fail to log the outbound click and worse, it’d also stop the link from being followed at all. We can adjust to handle this more gracefully however:

function trackOutboundLink (e, url) {
  if (window.ga && window.ga.create) {
    e.preventDefault();
    ga('send', 'event', 'outbound', 'click', url, {
      'transport': 'beacon',
      'hitCallback': function() {
        document.location = url;
      }
    });
  }
}

By checking for both window.ga and window.ga.create before calling preventDefault we ensure that the links work when Google Analytics is blocked, and that outbound clicks are tracked whenever we can.

Specify enumerator index offset

When looping through a collection of items, we sometimes want access to the index of an item at each iteration.

We can do that with collection.each_with_index{ |item, index| ... }.

In this case, index will always start at 0.

If we want index to start from a particular number, we can use Enumerator#with_index and specify an offset.

For example: collection.each.with_index(1){ |item, index| ... }.

Enumerator#with_index

Improving accessibility with visually hidden labels

Accessibility is something that’s always in the front of our minds when building websites at Icelab. It’s particularly important for Government work which requires specific WCAG adherence.

A pattern we use often is to visually hide additional descriptive labels. These labels will be announced by screen readers but not rendered on the screen. Take this common snippet:

ul.social-icons__list
  li.social-icons__list-item
    a.social-icons__anchor href="http://twitter.com/andymccray"
      i.fa.fa-twitter
  li.social-icons__list-item
    a.social-icons__anchor href="mailto:andy@icelab.com.au"
      i.fa.fa-envelope

With that we have a nice Twitter bird and envelope icon (courtesy of Font Awesome). Perfect!

…well, not so perfect really. We can make the experience much nicer for visually impaired users with a little extra effort:

ul.social-icons__list
  li.social-icons__list-item
    a.social-icons__anchor href="http://twitter.com/andymccray"
      span#twitter-label.hide-visually Follow Andy McCray on Twitter
      i.fa.fa-twitter aria-labelledby="twitter-label"
  li.social-icons__list-item
    a.social-icons__anchor href="mailto:andy@icelab.com.au"
      span#email-label.hide-visually Email Andy McCray
      i.fa.fa-envelope aria-labelledby="email-label"

We’re also using the aria-labelledby attribute to provide further clarification to screen readers.

And the CSS:

/*
 * Hide only visually, but have it available for screen readers:
 * http://snook.ca/archives/html_and_css/hiding-content-for-accessibility
 */

.hide-visually {
  border: 0;
  clip: rect(0 0 0 0);
  height: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  width: 1px;
}

And with that our screen reader users will have a much friendlier experience getting in touch.

URI regular expressions

Let’s go ahead and write some regex to validate a URI in Ruby:

Good Grief

URI.regexp

That’s it!

We can use it like so

URI.regexp.match(my_uri)

If you have a look at the output of URI.regexp in irb, you’ll see the pattern that’s used to match against. It’s relatively complex, but each capture group is documented.

  1. Scheme
  2. Opaque (e.g. scheme:foo/bar)
  3. User Info
  4. Host
  5. Port
  6. Registry
  7. Path
  8. Query
  9. Fragment
irb(main):001:0> URI.regexp.match("http://username:password@foo.bar:80/baz.html?query=string#fragment")
=> #<MatchData "http://username:password@foo.bar:80/baz.html?query=string#frag" 1:"http" 2:nil 3:"username:password" 4:"foo.bar" 5:"80" 6:nil 7:"/baz.html" 8:"query=string" 9:"frag">