B87c440334e086b6f4847a13c3a07f61

63 posts by obie-fernandez
@obie

Automatically set your Ngrok tunnel as default Rails host for development environment

Ever needed to test your Rails app's emails or background jobs in a local environment but also wanted to expose it through a public URL? Here's a handy trick to dynamically set your default URL options based on Ngrok's current public URL.

Note that you should fire up ngrok before starting your local server. Also this will just grab the first tunnel you have active.

Code: config/initializers/default_url_options.rb

if Rails.env.local?
  ngrok_results = `curl -s -X GET -H "Authorization: Bearer <NGROK_API_KEY>" -H "Ngrok-Version: 2" https://api.ngrok.com/tunnels`
  ngrok_results = JSON.parse(ngrok_results)
  public_url = ngrok_results.dig("tunnels", 0, "public_url")
  host = public_url.gsub("https://", "")
  Rails.application.routes.default_url_options = { host: host }
else
  Rails.application.routes.default_url_options[:host] = "<YOUR PRODUCTION HOST>"
end

How it Works:

  • Checks if the environment is local.
  • Fetches the current Ngrok public URL using the Ngrok API.
  • Parses the JSON response to get the public URL.
  • Updates the default_url_options for the Rails application with this public URL.

Note: Replace <NGROK_API_KEY> and <YOUR PRODUCTION HOST> with your actual keys and host.


Feel free to tweak it!

Verify existence of arbitrary email addresses from the command line

#!/usr/bin/env ruby
# frozen_string_literal: true

require 'resolv'
require 'net/smtp'

def mx_records(domain)
  Resolv::DNS.open do |dns|
    dns.getresources(domain, Resolv::DNS::Resource::IN::MX)
  end
end

def mailbox_exist?(email)
  domain = email.split('@').last
  mx = mx_records(domain).first
  return false unless mx

  Net::SMTP.start(mx.exchange.to_s, 25) do |smtp|
    smtp.mailfrom 'info@example.com' # replace with your email address or something more realistic
    smtp.rcptto email
  end
  true
rescue Net::SMTPFatalError, Net::SMTPSyntaxError
  false
end

if ARGV.length != 1
  puts "Usage: ruby #{__FILE__} <email_address>"
  exit 1
end

email = ARGV[0]
if mailbox_exist?(email)
  puts "Mailbox exists."
else
  puts "Mailbox doesn't exist or couldn't be verified."
end

Make Rails properly decode hashes and arrays in JSONB fields the way god intended

In what I'm going to call the greatest piece of pedantic fuckery of all time in Rails history, sgrif made JSON fields take primitives (including strings!!!!), instead of properly converting strings into Arrays and Hashes the way that God intended. In the years since, this one single peabrained decision, inexplicably rubber stamped by the rest of rails core, has surely cost millenias worth of headscratching, incontrollable sobbing, teeth gnashing, and rending of garments amongst poor Rails engineers like myself who wonder why, on an utterly non-deterministic basis, do my hashes turn into strings when going through the Postgres washing machine.

Unsure if you're having this problem yourself? Are you getting random no implicit conversion of Symbol into Integer (TypeError) errors in your code? That's what I'm talking about.

To fix this abomination and cast out the sgrif demon forever (or at least until they refactor ActiveRecord::Type modules again), simply toss the following file into your initializers and breathe easier.

# config/initializers/fix_active_record_jsonb.rb

ActiveRecord::Type::Json.class_eval do
  # this is a json field, thus always decode it
  def deserialize(value)
    ActiveSupport::JSON.decode(value) rescue nil
  end

  def serialize(value)
    if value.is_a?(::Array) || value.is_a?(::Hash)
      ::ActiveSupport::JSON.encode(value)
    elsif value.is_a?(::String) && value.start_with?("{", "[") && value.end_with?("}", "]")
      value
    elsif value.respond_to?(:to_json)
      value.to_json
    else
      value
    end
  end
end

Footnote: Apparently, I need to waste precious time of my life revisiting this topic every 5 years or so.

How to make Wisper properly load all subscribers in the app/subscribers directory structure

Rails.application.reloader.to_prepare do
  # Dev env will re-install subscribers on app reload
  Wisper.clear if Rails.env.development?

  Dir[Rails.root.join("app", "subscribers", "**", "*.rb")].each do |listener|
    relative_path = listener.gsub("#{Rails.root}/app/subscribers/", "")
    klass = relative_path.chomp(".rb").camelize.safe_constantize
    Wisper.subscribe(klass.new, async: !Rails.env.test?)
  end
end

Sharing since the sample provided by the project itself won't work with namespaced subscribers.

Fix Psych loading errors on wisper-sidekiq

One of my favorite rubygems is Wisper, a simple library that lets you add pub/sub style broadcasting and listeners to your app. (Been a fan since it came out in 2014, almost ten years ago!)

I tried to use wisper again recently, specifically with the wisper-sidekiq companion gem, which allows your subscribers to execute asynchronously as Sidekiq jobs.

Unfortunately, I immediately ran into an issue with Psych complaining about my parameters (ActiveRecord model instances) not being allowed by the YAML loading subsystem. If you're running into this same kind of issue, you'll know because you get exceptions that look like Tried to load unspecified class: Account (Psych::DisallowedClass)

Sidestepping the question of whether you should stick to only sending primitive object parameters (strings, integers, etc) as arguments to Sidekiq jobs, here is the monkeypatch solution to solving the problem, tested with wisper-sidekiq version 1.3.0.

Chuck this override into an initializer file.

module Wisper
  class SidekiqBroadcaster
    class Worker
      include ::Sidekiq::Worker

      def perform(yml)
        (subscriber, event, args) = ::YAML.unsafe_load(yml)
        subscriber.public_send(event, *args)
      end
    end
  end
end

The fix is the replacement of YAML.load with YAML.unsafe_load on line 16.

But Obie, isn't this dangerous? No. Objects passed in broadcast events are almost certainly not coming from the outside world in any way, shape, or form, so the reasons that you would typically be interested in blocking YAML loading from processing arbitrary objects do not apply.

But Obie, if your queues back up, won't you have stale data sitting in Redis as parameters to your jobs? For my particular use case this is not a concern, but for yours it might be. Either way it's not the job of the sidekiq-wisper library to enforce this constraint... and indeed, since the library predates the safety additions to YAML.load I'm not even sure that the author intended for the constraint to exist.

Now what would really be cool, and I wish I had time to implement this myself, is if sidekiq-wisper would automatically turn activerecord params into globalid identifiers and query them for you on the consumer side, the way that sidekiq does. Somebody should definitely implement something like that!

Fix inspect on Devise models

Have you wondered why User and other Devise models don't print properly in your console? Instead of nice pretty printed output, even if you're using a pretty printer, you still get a long, ugly, unreadable string.

Today I finally got fed up enough to do something about it, and here is the solution:

Chuck this into the bottom of your config/initializers/devise.rb file and you're good to go. It removes the overriding of the inspect method that is the culprit.

Devise::Models::Authenticatable.remove_method(:inspect)

But Obie, what about Chesterton's Fence!?!?!

My answer is that if you're paranoid about the possibility of inspect being called by a logger while a plain-text password happens to be in scope, then by all means override the method instead of just removing it, but doing so is left as an exercise to the reader. (Hint: start overriding it and Github CoPilot will do the rest.)

Extracting JSON Code with Nested Curly Braces in Ruby (the long painful way around, with help from GPT4)

Given a text string that contains JSON code with possible nested curly braces, I needed to extract the outermost JSON code, including the curly braces. Here's an example of such text, which you may recognize as the output of an LLM (specifically GPT in this case):

Here's the JSON you requested:

{
 "title": "Brainstorming ideas",
 "summary": "The user discussed exporting basic profile bots",
 "sentiment": "positive",
 "language": "English",
 "additional_information": {
    "tags": ["brainstorming", "bots", "automation"]
  }
 }

An initial crack at extracting just the JSON with a regex might look like this, but the outermost selector is too greedy.

> text.match /.*(\{.*\})/m; $1
=> "{\n    \"tags\": [\"brainstorming\", \"bots\", \"automation\"]\n  }\n }"

I took a hard look at that regex and figured that I just needed to make the first .* less greedy for it to work fine, like this:

/.*?(\{.*\})/m

And here is where the story gets more interesting (or stupid, depending on your perspective.) Because I tried it and unfortunately it DID NOT work. And since it was late at night and I was tired, instead of trying to figure out why I got frustrated and asked GPT4 to help me out.

GPT offered me this regex instead: /(?<=\{)(?s:.*)(?=\})/m

I've occasionally had to use PERL over the years so I was at least passingly familiar with the syntax here. It uses a positive lookbehind (?<=\{) to assert that the match starts after an opening curly brace, and a positive lookahead (?=\}) to assert that the match ends before a closing curly brace. The (?s:.*) part matches any characters, including newlines, between the curly braces, and finally, the /m flag is used for multi-line matching. Which should have worked fine, except that I'm coding in Ruby, so it generated this exception: undefined group option

It turns out that Ruby's regex engine does not support these operations (or if it does, I was not able to figure it out).

When I pointed this out to GPT4 it proposed a more straightforward and brute-force approach by writing a Ruby method that loops through the text and counts the opening and closing curly braces. This method can efficiently find the starting and ending indices of the outermost JSON object.

Here's the Ruby code to extract the JSON code from the text:

def extract_json(text)
  start_index = text.index('{')
  return nil if start_index.nil?

  end_index = start_index
  brace_count = 1

  text[start_index + 1..-1].each_char.with_index do |char, index|
    brace_count += 1 if char == '{'
    brace_count -= 1 if char == '}'
    end_index += 1

    break if brace_count.zero?
  end

  text[start_index..end_index]
end

This code defines a method called extract_json that takes the input text as an argument. It then loops through the text to find the starting and ending indices of the outermost JSON object by counting the opening and closing curly braces. Once the outermost JSON object is found, it is returned as a string.

It passed my tests so I committed the code and called it a night. But that first failed regex solution still bothered me this morning, and eventually I opened a terminal and started playing around with it again. Making that first .* operator less greedy should work.

And guess what? It does work.

> text
=> "Here is your JSON:\n\n```\n{\n \"title\": \"Brainstorming ideas\",\n \"summary\": \"The user discussed exporting basic profile bots\",\n \"sentiment\": \"positive\",\n \"language\": \"English\",\n \"additional_information\": {\n    \"tags\": [\"brainstorming\", \"bots\", \"automation\"]\n  }\n }\n```\n"

> text.match /.*?(\{.*\})/m; $1
=> "{\n \"title\": \"Brainstorming ideas\",\n \"summary\": \"The user discussed exporting basic profile bots\",\n \"sentiment\": \"positive\",\n \"language\": \"English\",\n \"additional_information\": {\n    \"tags\": [\"brainstorming\", \"bots\", \"automation\"]\n  }\n }"

Why I couldn't get it to work yesterday? No idea. But I did learn a lesson about trusting GPT4 over my own instinct. I'm going to call it a double-edged sword. You got to be careful you don't cut yourself. This particular solution wasn't that big a deal, but I can imagine situations where it would be.

Interestingly enough, I pointed out the failure to GPT4 and it admitted that it made a mistake.

You are correct, and I apologize for my earlier explanation. The regex /.*?(\{.*\})/m indeed works for extracting the outermost JSON code, including when there are nested curly braces, as demonstrated in your example. The non-greedy .*? before the capturing group ensures that the regex starts matching at the first opening curly brace. The greedy .* inside the capturing group then matches all characters, including nested curly braces, until the last closing curly brace is found.

Thank you for pointing this out, and I apologize for the confusion in my earlier responses.

Smoothly Expanding Search Box with CSS Transitions and Tailwind CSS

Are you looking to create a search box that expands smoothly when it becomes active? You can achieve this effect using CSS transitions and Tailwind CSS.

To get started, create an HTML input element with the desired styling using Tailwind CSS classes. Then, add the transition-all and duration-500 classes to the input element to specify that all CSS properties should have a smooth transition effect over a duration of 500 milliseconds.

Finally, use the transform property to scale the width of the input element to the desired value when it becomes active. For example, you can use the focus:w-64 class to set the width to 64 Tailwind units when the input element is focused.

Here's the code for the HTML input element with the necessary Tailwind CSS classes:

<input type="text" placeholder="Search..." autocomplete="off"
       class="rounded-md bg-gray-400 border-gray-200
       border-2 text-gray-800 p-1 w-28 focus:bg-white
       focus:flex-1 focus:pr-12
       transition-all duration-500 transform focus:w-64">

By adding these classes, you should see a smooth transition effect as the search box expands when it becomes active. You can adjust the duration and transform values to customize the effect to your liking.

Quick Ruby client for Marqo (VectorDB)

Marqo is a cool vector DB that lets you store facts and then query them later with natural language. You can install Marqo using Docker, which I managed to do today on my M1 Mac. Just make sure you stop whatever ElasticSearch or OpenSearch instances you may already have running on your machine first, since Marqo wants to use its own.

Once you have it running, you can use the following Ruby class to access it in your Rails project.

require 'httparty'

class Marqo
  include HTTParty

  base_uri 'http://localhost:8882'

  def initialize(auth = { username: 'admin', password: 'admin' })
    @auth = auth
  end

  def store(index_name, name, detail)
    options = {
      headers: { 'Content-Type' => 'application/json' },
      body: [{name: name, detail: detail}].to_json
    }
    self.class.post("/indexes/#{index_name}/documents", options)
  end

  def search(index_name, query)
    options = {
      basic_auth: @auth,
      headers: { 'Content-Type' => 'application/json' },
      body: { q: query }.to_json
    }
    self.class.post("/indexes/#{index_name}/search", options)
  end

  def self.client
    @client ||= new
  end
end

The data from my personal TIL site is now migrated. Here's how I handled URL redirects in Cloudflare

To set up a subdomain redirect with Cloudflare while preserving the request path and parameters, you can use Page Rules.

In the Cloudflare dashboard for your domain, click on the Page Rules link in the Rules section of the main navigation menu on the left of the page.

Click on the Create Page Rule button to start configuring the redirect.

In the If the URL matches field, enter the pattern for the URLs you want to redirect, using a wildcard (*) to capture the path and query string. In this cae, the pattern is:

til.obiefernandez.com/*

Now click on Add a Setting and select Forwarding URL from the dropdown menu.

Select 301 - Permanent Redirect from the Status Code dropdown.

In the Enter destination URL field, enter the URL where you want to redirect your requests to, using the $1 placeholder to preserve the query string.

For example: https://til.magmalabs.io/$2

Click on Save and Deploy to create the Page Rule and enable the redirect.

Using OpenAI's TikToken in Ruby

The encoding used in the tiktoken library (and the Ruby binding discussed in this post) is a specific way of converting text into a sequence of tokens, which are then represented by their unique IDs. The encoding scheme is designed to work with OpenAI models like gpt-3.5-turbo and is based on the model's vocabulary and tokenizer.

There's a simple Ruby binding for TikToken made by Iapark that compiles the underlying Rust library. https://rubygems.org/gems/tiktoken_ruby

First add it to your Gemfile

gem "tiktoken_ruby"

Then use it in your code. The service module I wrote today to use it in my Rails app looks like this:

require 'tiktoken_ruby'

module TikToken
  extend self

  DEFAULT_MODEL = "gpt-3.5-turbo"

  def count(string, model: DEFAULT_MODEL)
    get_tokens(string, model: model).length
  end

  def get_tokens(string, model: DEFAULT_MODEL)
    encoding = Tiktoken.encoding_for_model(model)
    tokens = encoding.encode(string)
    tokens.map do |token|
      [token, encoding.decode([token])]
    end.to_h
  end
end

Here's what it looks like in practice.

irb> TikToken.count("Absence is to love what wind is to fire; it extinguishes the small, it inflames the great.")
=> 19

irb> TikToken.get_tokens("Absence is to love what wind is to fire; it extinguishes the small, it inflames the great.")
=>
{28878=>"Abs",
 768=>"ence",
 374=>" is",
 311=>" to",
 3021=>" love",
 1148=>" what",
 10160=>" wind",
 4027=>" fire",
 26=>";",
 433=>" it",
 56807=>" extingu",
 21168=>"ishes",
 279=>" the",
 2678=>" small",
 11=>",",
 4704=>" infl",
 986=>"ames",
 2294=>" great",
 13=>"."}

The encoding is essential for processing text with the OpenAI models, as it allows them to understand and generate text in a format that is compatible with their internal representations. In the context of the tiktoken library, the encoding is particularly helpful for estimating token counts in a text string without making an API call to OpenAI services.

Is it possible to render a "sidecar" partial for a ViewComponent?

Let's say I have

class MyComponent < ViewComponent::Base
end

and 2 view files in

app/components/my_component/my_component.html.erb
app/components/my_component/_some_partial.html.erb

In my_component.html.erb, I want to be able to do:

render "some_partial"

But without a special configuration, that looks under the views directory for said partial. I don't want to extract that partial to its own component, nor do I want it floating by itself in the view directory.

The first step is to tell Rails it can look for templates in the view components directory

class ApplicationController < ActionController::Base
  append_view_path "#{Rails.root}/app/components"

Keep in mind that view contexts are based on the currently executing controller, so <%= render :some_partial %> in a PostsController (even within a ViewComponent class) will look for a partial in a subdirectory /posts or /application.

To make sure Rails finds your partial, use an absolute path when you render it:

<%= render "/my_component/some_partial" %>

Hat tip to Roli in the StimulusReflex discord

Turbo Native wrappers are generally available and working well

I've been waiting for updates to this technology for a long time and today found out that it's already available.

The website is here: https://turbo.hotwire.dev/handbook/native

And there's a cool blog post about it here: https://blog.minthesize.com/turbo-native

Strip and downcase email addresses if you're using them as lookup keys for anything

Seems like something I need to learn every few years, and recently popped up on one of my side projects. If you're relying on email addresses as lookup keys, for instance in authentication/login scenarios, then standardize on lowercase and strip whitespace for good measure.

This came up when a customer was not able to get inbound email processing to work for his account. Turns out that his user record had a properly downcased email, but his SMTP server was sending his email address with capitalization. Some string massaging fixed the immediate issue.

User.find_by(email: reply_params["sender"].to_s.downcase)

After putting this fix in, I found everywhere else in the system that email addresses come in as input and gave them the same treatment.

Migration operation that should run only in one direction

Disclaimer: I know it's not recommended to do data mutation in schema migrations. But if you want to do it anyway, here's how you do a one-way operation, using the reversible method.

class AddAds < ActiveRecord::Migration[5.0]
  def change
    create_table :ads do |t|
      t.string :image_url, null: false
      t.string :link_url, null: false
      t.integer :clicks, null: false, default: 0
      t.timestamps
    end

    reversible do |change|
      change.up do
        Ad.create(image_url: "https://www.dropbox.com/s/9kevwegmvj53whd/973983_AdforCodeReview_v3_0211021_C02_021121.png?dl=1", link_url: "http://pages.magmalabs.io/on-demand-github-code-reviews-for-your-pull-requests")
      end
    end
  end
end

Monthly streak display using SQL CTE in Rails

How to make a streak display like 750words.com

image

The database can do the heavy lifting for this kind of thing. A CTE (Common Table Expression) in a WITH clause to give me the set of days of the month that I would fill in with the completion data using a LEFT JOIN to make sure I got rows back even when there was no corresponding data on the right side.

# app/models/streak.rb
Streak = Struct.new(:user, :date) do
  # If you're wondering why we didn't use class extension syntax of Struct see the following link
  # https://tiagoamaro.com.br/2016/03/05/superclass-mismatch-structs-and-unicorn/
  SQL = "WITH dates(d) AS (
            SELECT generate_series(
              (date ?)::timestamp,
              (date ?)::timestamp,
              interval '1 day')
            )
            SELECT dates.d::date created_at, count(e.id) count FROM dates
            LEFT JOIN entries e on dates.d::date = e.created_at::date AND e.user_id = ?
            GROUP BY dates.d::date
            ORDER BY dates.d::date"


  def calendar
    Entry.find_by_sql([SQL, date.beginning_of_month, date.end_of_month, user.id])
  end
end

I'm knowingly abusing ActiveRecord by shoving the data I want to represent into Entry objects. I can be kinder to my future self and other maintainers by subsequently wrapping the returned objects into something more descriptive like StreakMonth or whatever.

Kick off async lambda processing from API Gateway

Asynchronous lambda invocation allows for a quick response back (e.g. 202 Accepted) while allowing the lambda to continue running. This would be desired if you wish to kick off a lambda that runs longer then the API GW 30 second timeout, or if the application needs a quick response without the need for direct success for failure of the longer running lambda.

At the time of this post this feature is not natively supported by serverless framework, but it looks trivial to set it up via a resource override. More information at https://github.com/serverless/serverless/issues/4862

Purchase my Serverless book at https://leanpub.com/serverless

VSCode live sharing rules for pair programming

https://visualstudio.microsoft.com/services/live-share/

If you're a fan of pair programming like I am and you haven't tried VSCode's live sharing functionality, then prepare to be delighted and amazed. It's breathtaking. You get multi-cursor support, and seamless sharing of local servers and terminals. Totally blew me away, and we will be using it for sure at Kickass Partners.

Dynamodb ttl expiration is not minute precise

Finally figured out why some of my expired items fail to delete when expected. (bold mine)

DynamoDB typically deletes expired items within 48 hours of expiration. The exact duration within which an item truly gets deleted after expiration is specific to the nature of the workload and the size of the table. Items that have expired and not been deleted will still show up in reads, queries, and scans. These items can still be updated and successful updates to change or remove the expiration attribute will be honored.

Bottom line: you can't rely on DynamoDB's ttl column based expiration to do minute-precise expiration of things in your serverless app. Luckily for my purposes, I don't need minute precision. I do however need hour precision, so I'm planning to write a little scheduled lambda that kicks off every sixty minutes and reaps whatever records have expired but not deleted yet, and delete them manually.


Purchase my Serverless book at https://leanpub.com/serverless

Limitations of Dynamodb KeyConditionExpression

Dynamodb has a query system with a particularly hard learning curve (in my opinion.) Even though I've been using it for years now, I still run into some sticky problems like this one today when I tried to use an IN operator in a query expression. Should be fine, right?

    const key = {
      IndexName: 'by_label_and_status',
      KeyConditionExpression:
        'labelId = :labelId and submissionStatus in (:sent, :opened, :listened)',
      ExpressionAttributeValues: {
        ':labelId': event.pathParameters.labelId,
        ':sent': 'sent',
        ':opened': 'opened',
        ':listened': 'listened',
      },
    };
    const result = await dynamodb.query('submissions', key);

The code above actually fails. Dynamodb is kind enough to report Invalid operator used in KeyConditionExpression: IN, but finding the reason why took me a bit of digging.

It turns out that indeed if you go to the guide for Dynamodb expression syntax, you'll see that the allowed comparators for KeyConditionExpression are limited.

Valid comparisons for the sort key condition are as follows:

sortKeyName = :sortkeyval - true if the sort key value is equal to :sortkeyval.

sortKeyName < :sortkeyval - true if the sort key value is less than :sortkeyval.

sortKeyName <= :sortkeyval - true if the sort key value is less than or equal to :sortkeyval.

sortKeyName > :sortkeyval - true if the sort key value is greater than :sortkeyval.

sortKeyName >= :sortkeyval - true if the sort key value is greater than or equal to :sortkeyval.

sortKeyName BETWEEN :sortkeyval1 AND :sortkeyval2 - true if the sort key value is greater than or equal to :sortkeyval1, and less than or equal to :sortkeyval2.

begins_with ( sortKeyName, :sortkeyval ) - true if the sort key value begins with a particular operand. (You cannot use this function with a sort key that is of type Number.) Note that the function name begins_with is case-sensitive.

Use the ExpressionAttributeValues parameter to replace tokens such as :partitionval and :sortval with actual values at runtime.

Since the system I'm working on is brand new, I was able to get the code example working by changing the values of our statuses. Instead of having completely disparate statuses for sent, clicked, and opened, I changed them to be sent, sent_clicked, and sent_opened. They are semantically correct, since clicked and opened states are also sent. That change allowed us to use begins_with instead of the prohibited IN operator.

    const key = {
      IndexName: 'by_label_and_status',
      KeyConditionExpression:
        'labelId = :labelId and begins_with(submissionStatus, :submissionStatus)',
      ExpressionAttributeValues: {
        ':labelId': event.pathParameters.labelId,
        ':submissionStatus': 'sent',
      },
    };

Purchase my Serverless book at https://leanpub.com/serverless

Careful sending complex objects to lambda callback

Ran into an issue today with the following code. It's the second or third time it's bitten me, and I think this time I finally am learning the general guideline: don't pass complex objects into your lambda success/failure callbacks!

export async function main(event, context, callback) {
  const id = event.pathParameters.id;
  try {
    const result = await startExecution(
      process.env.statemachine_arn, JSON.stringify({ id })
    );
    callback(null, success({ status: true, result }));
  } catch (error) {
    console.log(error.message);
    callback(error, failure({ status: false, message: error.message }));
  }
}

This lambda will fail with Converting circular structure to JSON exception, because the result object returned from the startExecution function is a big complex object that doesn't work with JSON.stringify. I've had the same thing happen to me with the results of axios requests, as well as other AWS service calls.

To fix, don't pass result but instead pass only what you need returned, or simply true.

Practical difference undefined vs null in JS

body = await sendEmail({
      from: 'Demos.Guru <support@demos.guru>',
      replyTo: submission.reviewerEmail || undefined,
      to: user.email,

Today I learned one of the practical differences between null and undefined thanks to my good friend and Kickass Partner Richard Kaufman https://richardkaufman.xyz.

In the code snippet shown, setting replyTo to undefined makes it disappear from the object, whereas setting it to null would leave the property in the object, with its value set to null. Makes perfect sense.

Save a DOM node to a global variable in Chrome 71

img I've often wanted to be able to query/manipulate a DOM node. Super easy to do now that you can right-click in the inspector and assign to a global variable.

More info at https://developers.google.com/web/updates/2018/10/devtools#store

How to properly do SNS in Serverless

Figuring this out has taken a few hours out of my day.

Assumption

Your system is comprised of multiple CloudFormation stacks (aka Serverless Framework services aka microservices)

What not to do

functions:
  dispatcher:
    handler: dispatcher.dispatch
    events:
      - sns: 'demo_submitted'

It is critically important to not create SNS topic subscriptions on the fly as lambda event triggers. The reason is that Serverless framework tries to be helpful by implicitly auto-magically creating resource declarations for those topics, effectively within the boundaries of the consumer service. Unless parts of your microservice are talking amongst themselves using SNS (which would be weird), you usually subscribe to SNS topics that originate in other services, not the one where you are consuming it.

Why not to do it

Let's say you try to subscribe to the same topic with a lambda in another service OR you go down the road of explicitly declaring topics as resources in the services that broadcast to the topics AND you've implicitly created those very same SNS topics already.

Then when you try to deploy you will get an error that looks something like this:

Serverless Error ---------------------------------------
  An error occurred: DemoSubmittedTopic - demo_submitted already exists in stack arn:aws:cloudformation:us-east-1:423:stack/producers-service-dev/5332a.

Resources must be unique and in this case, you already created that SNS topic in the consumer, not where it belongs and is expected to live.

What to do instead

When you subscribe a lambda to an SNS topic, use the ARN notation as described in https://serverless.com/framework/docs/providers/aws/events/sns#using-a-pre-existing-topic

functions:
  dispatcher:
    handler: dispatcher.dispatch
    events:
      - sns:
          arn: arn:xxx

The ARN notation in the subscription prevents the auto-magical undesirable creation of an SNS resource.

Vanity columns in join tables

Today as I was looking at a table in DynamoDB via the AWS console. This table is basically a queue of items for a particular user to look at or dismiss, and right now it only has two key columns: one for the user, and one for the item.

It struck me that it would be worthwhile to add a few extra columns to this table just to make administration and debugging a little easier. Namely, we could add the name of the user and the name of the item. At worst the tradeoff is that the read/write throughput for the table would be a little higher.

a Ruby Module is a Class that can be subclassed

Great article at http://tech.degica.com/en/2017/10/03/rubykaigi/ by Chris Salzberg

some of the most interesting features of a language are not the ones that are cleverly designed, but the ones that emerge out of a clever design

Rails 5.1 added :default option to belongs_to

From the changelog

image

Now if only someone would add it to has_one relationships. Tim says it's harder because then you're setting attributes on another object.

Don't Use the Word "Impactful," not worth the risk

Hat tip to my editor Cynthia for pointing out why I should never use the word impactful in my writing.

image

image

I laughed out loud when I got to that last part (highlight mine.)

Full explanation here

Getting Heroku Review Apps to Work

A few years ago I heard about a project called Fourchette, which facilitated setting up one Heroku app per pull request on a project (aka review apps). I remember being all like THAT'S FREAKING BRILLIANT! Then I went back to whatever I was doing and never did anything about it.

Well, this week I finally had the time and inclination to get review apps working on Heroku. The instructions are out there, but they gave me enough trouble that I figured I'd document the gotchas for posterity.

#1. Understand the app.json file, really

We already had a tiny app.json file that we had created in connection with getting Heroku CI to run our test suite. All it had was an environments section that looked like this:

"environments": {
  "test": {
     "env": {
      "DEBUG_MAIL": "true",
      "OK_TO_SEED": "true"
    },
    "addons":[
      "heroku-postgresql:hobby-basic",
      "heroku-redis:hobby-dev"
    ]

When I started trying to get review apps to work, I simply created a pull request, and followed the dashboard instructions for creating review apps, assuming that since we already had an app.json file that it would just work. Nope, not at all.

After much thrashing, what finally got me over the hump was understanding the purpose of app.json from first principles, which didn't happen until I read this description of the Heroku Platform API. App.json originally came about as a way to automate the creation of an entire Heroku project, not just a CI or Review configuration. It predates CI and Review Apps and has been in essence repurposed.

#2. Add all your ENV variables

The concept of ENV variables being inherited from the designated parent app really threw me for a loop at first. I figured that the only ENV variables needed to be declared in the env section of app.json would be the ones I was overriding with a fixed value. Wrong again.

After much trial-and-error, I ended up with a list of all the same ENV variables as my staging environment. Some with fixed values, but most just marked as required.

"env": {
    "AWS_ACCESS_KEY_ID": {
      "required": true
    },
    "AWS_SECRET_ACCESS_KEY": {
      "required": true
    },

This won't make sense if you're thinking that app.json is specifically for setting up Review Apps (see #1 above.)

#3. Understand the lifecycle, especially with regards to add-ons

After everything was mostly working (meaning that I was able to get past the build stage and actually access my web app via the browser) I still kept getting errors related to the Redis server being missing. To make a long story short, not only did I have to add it to the addons section, but I also had to delete the review app altogether and create it again, so that addons would be created. (Addons are not affected by redeployment.)

"addons":[
  "heroku-postgresql:hobby-basic",
  "heroku-redis:hobby-dev",
  "memcachier:dev"
],

In retrospect, I realize that the reason that was totally unclear is that my review apps Postgres add-on was automatically created, even before I added an addons section to app.json. (Initially I thought it was coming from the test environment.)

I still don't know if Postgres is added by default to all review apps, or inherited from the parent app.

4. Post deploy to the rescue

There's at least one thing you want to do once, every time a new review app is created, and that is to load your database schema. You probably want to seed data also.

"scripts": {
  "postdeploy": "OK_TO_SEED=true bundle exec rails db:schema:load db:seed"
}

As an aside, I have learned to put an OK_TO_SEED conditional check around destructive seed operations to help prevent running in production. This is especially important if you run your staging instances in production mode, like you should.

How to setup Heroku Rails app to handle yarn.lock

One of the nicest features of Rails 5 is its integration with Yarn, the latest and greatest package manager for Node.js. Using it means you can install JavaScript dependencies for your app just as easily as you use Bundler to install Ruby gems.

Now one of the biggest problems you face when using any sort of Node package management is that the combinatorial explosion of libraries downloaded in order to do anything of significance.

Given that reality, you really do not want to add node_modules to your project's git repository, no more than you would want to add all the source code of your gems. Instead, you add node_modules to your .gitignore file.

Yarn adds a file to the root of your Rails app called yarn.lock. Today I learned that if you include the Node.js buildpack to your project on Heroku, it will recognize yarn.lock and install any required node modules for you. You just have to make sure that it runs first in the build chain.

heroku buildpacks:add --index 1 heroku/nodejs

Side note: If you use Heroku CI then you'll need to setup your test environment with the extra buildpack also by adding a new section to app.json.

"buildpacks": [
{ "url": "heroku/nodejs" },
{ "url": "heroku/ruby" }
]

Note that the nodejs buildpack expects a test script to be present in package.json. If you don't have one already, just add a dummy directive there. Almost anything will work; I just put an echo statement.

"scripts": {
    "test": "echo 'no tests in js'"
  },

Cloudflare Flexible SSL mode breaks Rails 5 CSRF

Putting this out there since I didn't find anything on StackOverflow or other places concerning this problem, which I'm sure I'm not the first to run into. CloudFlare is great, especially as a way to set-and-forget SSL on your site, along with all the other benefits you get. It acts as a proxy to your app, and if you set its SSL mode to Flexible then you don't have to have an SSL certificate setup on your server. This used to be a big deal when SSL certificates were expensive. (You could argue that since Let's Encrypt and free SSL certificates it's not worth using Flexible mode anymore.)

Anyway, I digress. The point of this TIL is that if you proxy https requests to http endpoint in Rails 5, you'll get the dreaded InvalidAuthenticityToken exception whenever you try to submit any forms. It has nothing to do with the forgery_protection_origin_check before action in ApplicationController.

The dead giveaway that you're having this problem is in your logs. Look for the following two lines near each other.

WARN -- : [c2992f72-f8cc-49a2-bc16-b0d429cdef20] HTTP Origin header (https://www.example.com) didn't match request.base_url (http://www.example.com)  
...
FATAL -- : [c2992f72-f8cc-49a2-bc16-b0d429cdef20] ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken): 
Aug 13 18:08:48 pb2-production app/web.1: F, [2017-08-14T01:08:48.226341 #4] FATAL -- : [c2992f72-f8cc-49a2-bc16-b0d429cdef20]    

The solution is simple. Make sure you have working SSL and HTTPS on Heroku (or wherever you're serving your Rails application.) Turn Cloudflare SSL to Full mode. Problem solved.

Run Puma in Single mode for development

Turns out how to switch between single and clustered modes of Puma is super unclear in the (little to non-existent) documentation. You'd think that setting WEB_CONCURRENCY to 1 would do it, but you actually have to set it to zero. Meaning you don't want to spin up any child processes.

return while rendering partial collections

Ruby's next keyword only works in the context of a loop or enumerator method.

So if you're rendering a collection of objects using Rails render partial: collection, how do you skip to the next item?

Since partials are compiled into methods in a dynamically generated view class, you can simulate next by using an explicit return statement. It will short-circuit the rendering of your partial template and iteration will continue with the next element of the collection.

For example

# app/views/users/_user.haml
- return if user.disabled?
  %li[user]
    rest of your template...

Using default_url_options in RSpec with Rails 5

There's a long-standing bug in the integration of controller testing into RSpec that prevents you from easily setting default_url_options for your controller specs. As far as I can tell, it doesn't get fixed because the RSpec teams considers the problem a bug in Rails, and the Rails team does not care if RSpec breaks.

I'm talking about the issue you run into when you're trying to work with locale settings passed into your application as namespace variables in routes.rb like this:

scope "/:locale" do
    devise_for :users,  #...and so on

Today I learned that the inability to set a default :locale parameter can be maddening. Your specs will fail with ActionView::Template::Error: No route matches errors:

1) Devise::RegistrationsController POST /users should allow registration
     Failure/Error: %p= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token)

     ActionView::Template::Error:
       No route matches {"action":"show","confirmation_token":"pcyw_izS8GchnT-R3EGz","controller":"devise/confirmations"} missing required keys: [:locale]

The reason is that ActionController::TestCase ignores normal settings of default_url_options in ApplicationController or your config/environments/test.rb. No other intuitive attempt at a workaround worked either. Frustratingly, it took me around an hour to debug and come up with a monkeypatch-style workaround. The existing workarounds that I could find online are all broken in Rails 5.

So here it is:

# spec/support/fix_locales.rb
ActionController::TestCase::Behavior.module_eval do
  alias_method :process_old, :process

  def process(action, *args)
    if params = args.first[:params]
      params["locale"] = I18n.default_locale
    end
    process_old(action, *args)
  end
end

Note the assumption that you are passing params in your spec using a symbol key and not the string "params".

CSS Autoprefixer OMG!!!

Posting on Rails channel, since there is a gem for using this amazing tool with your Rails apps. Using Autoprefixer, you no longer have to worry about writing or maintaining vendor-specific CSS properties. (The ones with the dash prefixes.) You just use the latest W3C standards, and the rest is taken care of for you with post-processing.

FactoryGirl, WebMock, VCR, Fog and CarrierWave

In the interest of fast suite runs (amongst other reasons) you want to make sure that your specs are not dependent on remote servers as they do their thing. One of the more popular ways of achieving this noble aim is by using a gem called WebMock, a library for stubbing and setting expectations on HTTP requests in Ruby.

The first time you use WebMock, code that calls external servers will break.

WebMock::NetConnectNotAllowedError:
       Real HTTP connections are disabled. Unregistered request: GET https://nueprops.s3.amazonaws.com/test...

       You can stub this request with the following snippet:

       stub_request(:get, "https://nueprops.s3.amazonaws.com...

Now maintaining that stub code is often painful, so you probably want to use a gem called VCR to automate the process. VCR works really well. After instrumenting your spec correctly, you run it once to generate a cassette, which is basically a YAML file that captures the HTTP interaction(s) of your spec with the external servers. Subsequent test runs use the cassette file instead of issuing real network calls.

Creation and maintenance of cassettes that mock interaction with JSON-based web services is easy. Services that talk binary? Not so much. And almost every modern Rails project I've ever worked on uses CarrierWave (or Paperclip) to handle uploads to AWS. If you try to use VCR on those requests, you're in for a world of annoyance.

Enter Fog, the cloud-abstraction library that undergirds those uploader's interactions with AWS S3. It has a somewhat poorly documented, yet useful mock mode. Using this mode, I was able to make WebMock stop complaining about CarrierWave trying to upload fixture files to S3.

However, the GET requests generated in my specs were still failing. Given that I'm using the venerable FactoryGirl gem to generate my test data, I was able to eventually move the stub_request calls out of my spec and into a better abstraction level.

factory :standard_star do
  sequence(:name) { |n| "Cat Wrangler #{n}" }
  description "Excellence in project management of ADD people"
  icon { Rack::Test::UploadedFile.new('spec/support/stars/cat-wrangler.jpg') }
  image { Rack::Test::UploadedFile.new('spec/support/stars/cat-wrangler.jpg') }
  after(:create) do |s, e|
    WebMock.stub_request(:get, "https://nueprops.s3.amazonaws.com/test/uploads/standard_star/image/#{s.name.parameterize}/cat-wrangler.jpg").
             to_return(:status => 200, :body => s.image.file.read)

    WebMock.stub_request(:get, "https://nueprops.s3.amazonaws.com/test/uploads/standard_star/icon/#{s.name.parameterize}/cat-wrangler.jpg").
             to_return(:status => 200, :body => s.icon.file.read)

  end
end

HTML5 Date Input doesn't work on Firefox #wtf

This one caught me by surprise today. Luckily, it's relatively simple to detect the missing functionality using Modernizr.js and use Datepickr instead.

$(function(){           
  if (!Modernizr.inputtypes.date) {
    $('input[type=date]').datepicker({
      dateFormat : 'yy-mm-dd'
    });
  }
});

http://stackoverflow.com/a/30503903/626048

When counter_cache on wrong side of association

Absentmindedly put a counter_cache declaration on the has_many instead of where it belongs (pun intended.)

Rails 5 will complain in the most cryptic way it possibly can, which is to raise the following exception

ActiveModel::MissingAttributeError: can't write unknown attribute `true`

If you get that error, now you know how to fix it. Good luck and godspeed.

Change Rails default generators to Sass

Rails inexplicably defaults to SCSS when generating stylesheets. Maybe for the same reasons that DHH doesn't like Haml?

Anyway, to fix it just add the following directive to config/environments/development.rb:

config.sass.preferred_syntax = :sass

Rails 5 Attributes API + JSONb Postgres columns

As of when I'm writing this (Jan 2017), support for using ActiveRecord store with Postgres JSONb columns is a bit of shit-show. I'm planning to help fix it as soon as I have some time to spare, but for the moment if you want a better way of supporting these valuable column types in your Rails 5 app, use the new Attributes API. Plus get much improved performance with the Oj gem.

Here's how to make it work. First, define a :jsonb type to replace the native one.

class JsonbType < ActiveModel::Type::Value
  include ActiveModel::Type::Helpers::Mutable

  def type
    :jsonb
  end

  def deserialize(value)
    if value.is_a?(::String)
      Oj.load(value) rescue nil
    else
      value
    end
  end

  def serialize(value)
    if value.nil?
      nil
    else
      Oj.dump(value)
    end
  end

  def accessor
    ActiveRecord::Store::StringKeyedHashAccessor
  end
end

Next, register it in an initializer.

ActiveRecord::Type.register(:jsonb, JsonbType, override: true)

Note that the JsonbType class will need to be somewhere in your loadpath.

Now just declare the attribute at the top of your ActiveRecord model like this:

class User < ApplicationRecord
  attribute :preferences, :jsonb, default: {}

Ruby has a safe operator, no need to try anymore

Wow, how did I miss this memo? Ruby 2.3 introduced a safe operator

Instead of

current_user.try(:profile).try(:bio)

you can now do

current_user&.profile&.bio

ActiveRecord test objects made easy

If you're testing a ActiveRecord model mixin in your application, you might be tempted to unit test it in the context of one of your app's models. However, that would violate your test isolation and introduce complexities related to the behavior of the model.

Better solution is to make an Active Record class just for your test, and the fact that you can invoke schema definitions on the fly makes it super easy. Here's the top of one of my specs, illustrating the technique.

require 'rails_helper'

ActiveRecord::Schema.define do
  create_table :test_objects, force: true do |t|
    t.jsonb :jobs, null: false, default: {}
  end
end

class TestObject < ApplicationRecord
  include WorkerRegistry
end

RSpec.describe WorkerRegistry do
  let(:test_object) { TestObject.create }

  ...

Adding your own datetime formats to Rails

An aspect of Rails that I adore is how it has a place for nearly everything you need to do. One of those things is to format dates/times using the strftime method. Instead of tucking away custom strftime patterns in constants, you can configure them onto the native Rails formatter, accessed via time.to_s(:format_name)

DateTime formats are shared with Time and stored in the Time::DATE_FORMATS hash. Use your desired format name as the hash key and either a strftime string or Proc instance that takes a time or datetime argument as the value.

# config/initializers/time_formats.rb
Time::DATE_FORMATS[:month_and_year] = '%B %Y'
Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") }

Here's one of the formats that I've been using lately, to get my times into a more familiar form.

Time::DATE_FORMATS[:short_time] =
   lambda { |time| time.strftime('%I:%M%p').gsub('AM','am').gsub('PM','pm').gsub(':00','') }

Easily add Toastr flash notices to Rails apps

I learned about Toastr JavaScript library last week and have been delighted to use it instead of more traditional flash messaging.

First of all get the Toastr sources. I opted to link to them on CDNJS:

= stylesheet_link_tag 'https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.3/toastr.min.css'
= javascript_include_tag 'https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.3/toastr.min.js'

Next I defined some extra flash types in my application_controller.rb file to match Toastr's native notification types and enable use of the built-in styling.

class ApplicationController < ActionController::Base
  add_flash_types :success, :info, :warning, :error
  ...

Finally, add the following block of JavaScript to the bottom of a layout template (or whatever shared partial that contains your JS and CSS includes.

- flash.keys.each do |key|
  - toastr_key = key
  - toastr_key = 'info' if key == 'notice'
  - toastr_key = 'warning' if key == 'alert'
  :javascript
    $(function() {
      toastr["#{toastr_key}"]("#{flash[key]}");
    });

Lines 2 and 3 establish a mapping from conventional Rails notice and alert so that I don't have to hack libraries like Devise which rely on them.

Easy.

Don't roll your own slug code, use FriendlyId

This finding was a pleasant surprise. For years, I've been writing the same kind of boilerplate code to override to_param on my model classes and generate unique slugs. Turns out there's a really well-written library that does that, with some worthwhile additional functionality. Check out FriendlyId for easy slug generation and even the ability to preserve history of slugs after changes, so that it's possible to do 301 redirects with just a couple lines of code.

Use fetch method to access ENV variables in Ruby

It's better for at least a couple of reasons, eloquently stated in this blog post by Michal Orman

...we can set default values or handle - providing a block - gracefully missing keys. Also using fetch with unknown key will raise KeyError that will tell us which exactly key is missing. That is in fact the behavior we are expecting from the app. Without required settings is just not working and complaining about missing setting and not about some random nil references.

Got that? Instead of this:

AWS.config(
  access_key_id:      ENV['S3_ACCESS_KEY'],
  secret_access_key:  ENV['S3_SECRET_KEY'],
  region:             ENV['S3_REGION']
)

Do this:

AWS.config(
  access_key_id:      ENV.fetch('S3_ACCESS_KEY'),
  secret_access_key:  ENV.fetch('S3_SECRET_KEY'),
  region:             ENV.fetch('S3_REGION')
)

Set defaults for JSONb postgres columns in Rails

Make sure to pass the migration a native Ruby hash as the default value. DO NOT pass it a string representation of an hash, thinking that it'll work (as valid JSON).

DO THIS

t.jsonb :preferences, default: {}, null: false

NOT

t.jsonb :preferences, default: '{}', null: false

It'll break in a maddeningly non-obvious way. Take my word for it. Also there is this relevant StackOverflow post which saved my ass.

Use Nodemon to auto-restart Rails server

The handy-dandy Nodemon tool is not just for Node. Today I whipped up an invocation that can restart my Rails server whenever there are changes in the config directory tree. Super useful when working heavily with i18n, since changing translation files requires bouncing the server to see changes reflected in the view.

$ nodemon --watch config -e rb,yml --exec "rails server"

Keyword arguments in Ruby 2

Ruby 2 gained keyword arguments while I was away in Javascript-land the last few years, and somehow I missed the memo. Loving the elegance of how you specify defaults and required parameters.

Thoughtbot has the best writeup.

I suspect I will not be using non-keyword arguments in my Ruby code anymore.

Don't try to modify strings that come out of ENV

Today I woke up to an exception RuntimeError: can't modify frozen String

I looked at the code that had raised the exception. It was doing a gsub! on a value out of params. It took me a bit of digging to figure out why it failed. Turns out there's an edge case that causes one of those values to get pulled out of ENV instead of the normal flow where it originates from the user.

Strings coming out of ENV are frozen. D'oh!

Approval Tests are a thing

In normal unit testing, you say expect(person.calculate_bmi).to eq(21) or something like that. Approvals allow you to assert the state of complex objects against a known state.

For example, you can say, Approvals.verify(person)

I had not heard of this approach, which is posed as an alternative to traditional TDD. http://approvaltests.com

Dropbox is ending support for public folders

File this one under huge pain in the ass... Dropbox is ending support for public folders as of September 17th, 2017. Luckily that's a lot of lead time.

Description of the new Shared Files functionality that replaces public folders.

Use the browser to validate email fields

You are using HTML5 typed text input fields, aren't you?

If you make an input element with type="email" and you make it required, then the browser will take care of making sure that the email is valid on form submit. Automatically.

Doesn't eliminate the need for validation on the server side, or does it?

Greenkeeper.io automated dependency management

This product just blew my mind, since I know how difficult it is to keep up with Node dependencies. You could manually track updates of your dependencies and test whether things continue to work. But it takes a lot of effort. So most of the time, your software is in a Schrödinger state of being potentially broken, and you have no idea until you run npm install and try the next time.

Greenkeeper handles the chores of dependency management. npm install and npm test is called immediately after an update. There is no doubt about the state of your software.

https://greenkeeper.io/

Better enumerated types in Active Record

Rails 4 added support for enumerations in Active Record classes. That's cool, but what's cooler is how it has been reimagined by Foraker Labs in Denver, based on the seriously underrated gem Enumerated Type.

Please go read the blog post about it right now, it'll take 5-10 minutes and I promise you won't regret it.

Debug Network Activity in Google Chrome

Today I learned about the existence of Google Chrome Net Internals, a utility that gives you extensive abilities related to debugging network activity in your browser. Used it as part of trying to diagnose why some of my Rails project links were hanging in development mode.

So you CAN disable swipe navigation in Chrome

Damn, I can't believe that I didn't think of disabling this dubious feature sooner. Can't even begin to tell you how often an errant fingertip brush makes my Chrome history go backwards (or forwards, for that matter). Turn that shit off with the following command in your terminal.

defaults write com.google.Chrome AppleEnableMouseSwipeNavigateWithScrolls -bool NO

Make sure to restart Chrome for it to take effect.

Clearfixing is much easier these days

Hadn't needed to do a clearfix in awhile and thought I would have to do something like this:

.container::after {
    content:"";
    display:block;
    clear:both;
}

Or even worse...

.container::before, .container::after {
    content:"";
    display:table;
}
.container::after {
    clear:both;
}
.container {
    zoom:1; /* For IE 6/7 (trigger hasLayout) */
}

Then I was pleasantly surprised to learn that on modern browsers you just have to set the overflow property to auto on the containing element and you should be good to go.

.container {
  overflow: auto;
}

The most useful CSS printing directive

@media print {
    div {page-break-inside: avoid;}
}

Should be self-explanatory. Note that you can't use this property on absolutely positioned elements, but other than that, it pretty much works perfectly for preserving logical chunks of content in printed output.

Why Capybara requires button for submitting forms

Despite being a super common request, Capybara's API doesn't give you a way to submit forms directly (without hitting a submit button). The denial to do so is actually a principled stance, as you can read for yourself in this pull request. In a nutshell, Jonas believes its a bad practice to do so, plus there is no standard way to support the functionality across all browsers.

Workarounds exist, but seem clunky.

How Turbolinks handles redirects

When you visit location /one and the server redirects you to location /two, you expect the browser’s address bar to display the redirected URL. However, Turbolinks makes requests using XMLHttpRequest, which transparently follows redirects. There’s no way for Turbolinks to tell whether a request resulted in a redirect without additional cooperation from the server.

To work around this problem, Rails sends a Turbolinks-Location header in response to a visit that was redirected using redirect_to, and Turbolinks will replace the browser’s topmost history entry with the value provided. If for some reason you are performing redirects manually (so-to-speak, without using the redirect_to helper method), then you'll have to take care of adding the header yourself.

Exec a Login Shell

While setting up the Droplet that's hosting this site, I had to switch from root to the rails user several times. In order to get gems and bundler to work properly, I needed a login shell, which you don't get automatically just using su. The solution is to exec bash -l after su.

What I didn't already know is exactly why that command does what it does. Turns out that exec replaces the current process (my shell) instead of starting a new sub-process. So while just typing bash -l will also give you the intended result, it's not as efficient.

Use Axios for HTTP requests in Javascript

Axios is a slick promise-based HTTP client that works both in all modern browsers and server-side node.js. I like the simplicity of its interface.

axios.get('/user?ID=12345')
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

...and the fact that it supports concurrent requests without too much hassle

function getUserAccount() {
  return axios.get('/user/12345');
}

function getUserPermissions() {
  return axios.get('/user/12345/permissions');
}

axios.all([getUserAccount(), getUserPermissions()])
  .then(axios.spread(function (acct, perms) {
    // Both requests are now complete 
  }));