Designing HTML emails

The 90s are calling: they want their tables back. Unfortunately, you need them all for laying out your HTML emails.

Email client HTML rendering is way more scattered than browser HTML. While you might have a pretty good understanding of what features and patterns you can use to support all major browsers, I doubt anyone masters this craft for HTML email clients.

The only way to ensure your email looks good (acceptable, at least) in all mail clients, is to check it. Litmus is your go-to solution for this (see below). W...

Project management best practices: Working with clients in person

When working on a bigger project, the easiest way to improve your work relation with a client or an external product manager, is to make sure you see them in person once in a while.

It makes sense to meet each other when you start working together to establish a relationship and find out what makes them tick.

If you need to discuss a larger package of work, use the opportunity and meet up and discuss it in person.

When you have to discuss something in your daily work, prefer talking to writing, and consider using a webcam.

It's OK to put block elements inside an <a> tag

In general, you should not put a block element inside an inline element. So don't do this:

<span>
  <div>text</div>
</span>

The browser will think you wrote invalid HTML by accident, and will sometimes reorder elements silently.

There is one notable exception: It's OK to wrap block elements in a <a> tag in HTML5 (not 4). The spec says:

The a element may be wrapped around entire paragraphs, lists, tables, and so forth, even entire sections, so long ...

tig: install a more recent version

I noticed that tig 2.5.1 that is provided by Ubuntu 20.04 repositories has inferior bash completion than older versions after a complete rewrite. Newer versions, however, received some fixes. This inspired me to upgrade tig.

The official debian repositories have more recent versions of tig than Ubuntu does.

Capistrano task to edit staging / production credentials

When using Rails credentials, you will sometimes want to edit the encrypted credentials for staging or production environments. To do that you need the secret key which should only live on the servers.

Warning

Do not just download these keys to your local dev environment. These files are sensitive and should not simply lie around on your machine.

Instead, add the attached capistrano task to your project (as lib/capistrano/tasks/credentials.rake).

Now you can say

cap <environment> credentials:edit

to open an editor with ...

Ruby / Rails: clone vs. dup vs. deep_dup

Ruby and Rails have several methods for creating a new object that looks like another: clone, dup, deep_dup. When using them you should be aware of their differences so that you can select the method you really need.

clone

  • Shallow copy: references to other objects/values are copied (instead of cloning those objects/values)
  • Clones the object and all its "special object attributes" like frozen, tainted and modules that the object has been extended with
  • [Ruby 2.6 documentation for clone](https://devdocs.io/ruby~2.6/obj...

Dealing with I18n::InvalidPluralizationData errors

When localizing model attributes via I18n you may run into errors like this:

I18n::InvalidPluralizationData: translation data { ... } can not be used with :count => 1. key 'one' is missing.

They seem to appear out of the blue and the error message is more confusing than helpful.

TL;DR A model (e.g. Post) is lacking an attribute (e.g. thread) translation.
Fix it by adding a translation for that model's attribute (attributes.post.thread). The error message reveals the (wrongly) located I18n data (from `attributes.thread...

Unpoly 3.9.1, 3.9.2 and 3.9.3 released

3.9.3

  • Fix an error being thrown when a caching request is tracking an existing request to the same URL, and that existing request responds with an error status (issue #676).
  • Fix a bug where a modal overlay could not be closed when a child popup would be open below the screen fold.
  • Focus is no longer trapped in popup overlays. Focus remains trapped in all other overlay modes, but this can be disabled by setting up.layer.config.overlay.trapFocus = false.
  • The dismiss button in overlays now...

A different testing approach with Minitest and Fixtures

Slow test suites are a major pain point in projects, often due to RSpec and FactoryBot. Although minitest and fixtures are sometimes viewed as outdated, they can greatly improve test speed.

We adopted a project using minitest and fixtures, and while it required some initial refactoring and establishing good practices, the faster test suite was well worth it! Stick with me to explore how these tools might actually be a good practice.

So, why is this setup faster? Partially, it's because minitest is more lightweight than RSpec, which...

Running Cucumber deletes my whole app!

If a Cucumber run deletes your application directory, an integration fail between Capybara and Capybara Screenshot may be the cause. Capybara Screenshot defaults to storing screenshots in ., and tidying up screenshots happens to "tidy up" the application as well. Seen with Capybara 2.18 and Capybara Screenshot 1.0.4.

Upgrading Capybara Screenshot to 1.0.26 fixes the issue. Consider also setting Capybara.save_path = 'tmp/capybara' in an initializer.

Updated: Ruby: Debugging a method's source location and code

Newer Rails projects come with a gem that allows you to access .method(:foo).source. Added a corresponding section to a fitting card

Timeouts for long-running SQL queries

While the main goal always is to prevent long-running queries in the first place, automatic timeouts can serve as a safety net to terminate problematic queries automatically if a set time limit is exceeded. This prevents single queries from taking up all of your database’s resources and reduces the need for manual intervention that might destabilize or even crash the application.

As Rails does not set a timeout on database statements by default, the following query will run for an entire day:

ActiveRecord::Base.connection.execute("S...

Ruby: Replacing Unicode characters with a 7-bit transliteration

Sometimes you need to remove high Unicode characters from a string, so all characters have a code point between 0 and 127. The remaining 7-bit-encoded characters ("Low-ASCII") can be transported in most strings where escaping is impossible or would be visually jarrring.

Note that transliteration this will change the string. If you need to preserve the exact string content, you need to use escaping.

Using ActiveSupport

ActiveSupport comes with a `#tran...

Be careful when copy & pasting code from the web to your terminal

What you copy may not be what you see in the browser.

Here is an online tool to determine the exact code points of your Unicode string: https://devina.io/unicode-analyser

RSpec: Increase readability with super_diff

When handling nested hashes the RSpec output is often hard to read. Here the gem super_diff could help.

Add super_diff to your project

  1. Add super_diff to your Gemfile:
gem 'super_diff'
  1. Require it in your spec_helper.rb
require 'super_diff/rspec' # For Rails applications you can replace this with 'super_diff/rspec-rails'
  1. Customize colors in spec/support/super_diff.rb
SuperDiff.configure do |config|
  config.ac...

Don't compare datetimes with date ranges in MySQL and PostgreSQL

When selecting records in a date range, take care not to do it like this:

start_date = Date.parse('2007-05-01')
end_date = Date.parse('2007-05-31')
LogItem.where(:created_at => start_date .. end_date)

The problem is that created_at is a datetime (or Time in Ruby), while start_date and end_date are simple dates. In order to make sense of your query, your database will cast your dates to datetimes where the time component is 00:00:00. Because of this the query above will lose records created from `2007-05-31 00:00:0...

Chrome Lighthouse

Chrome has a built-in utility to check performance and accessibility (and more) of your web app: Lighthouse.

Open the Developer Tools and go to the lighthouse tab:

Image

Then you'll see some suggestions on how to improve your site.
This is cool, because you can even use it with non-public pages or your development environment (but be aware that some settings we're using for development, like not minifying JS and CSS files, might ruin your stats)...

Using tig

tig is a command line explorer for Git that is just awesome. Install via apt-get or brew.

Handy commands

  • t ("tree"): Directory-structure based access. You'll see the current directory annotated with the latest change date and its author. Navigate with arrow keys or vim.
  • b ("blame"): Opens the file under the cursor and annotates each line with change date and author.
  • d ("diff"): Like ENTER on a commit, but arrow keys will scroll the diff!
  • /: Search current view (e.g. commit list, diff). Jump to next hit with n....

Learn how to use ruby/debug

This talk shows simple and advanced usages of the ruby/debug debugger. It goes through a step by step debugging workflow.

Here are some command examples:

(rdbg) step 2 # step twice
(rdbg) info # show current scope, including self
(rdbg) bt # show backtrace
(rdbg) frame 3 # go directly to frame 3
(rdbg) break User#email # add a breakpoint in the email instance method
(rdbg) catch SomeException # break when SomeException is raised

Some advanced exam...

Open Terminator from nautilus context menu

On our Ubuntu machines we have nautilus file manager with nautilus-extension-gnome-terminal installed. This adds an entry to the context menu (right click) to start a gnome-terminal in the current directory. As I'm mostly using Terminator terminal, I wanted to have a similar context menu entry to launch Terminator directly. I came across this python script that does exactly that.

  • Install python3-nautilus: sudo apt install python3-nautilus
  • Create `/usr/share/nautilus-...

Using the ActiveSupport::BroadcastLogger

The ActiveSupport::BroadcastLogger allows you to log to multiple sinks. You know this behavior from from the rails server command, that both logs to standard out and the log/development.log file.

Here is an example from the ActiveSupport::BroadcastLogger API:

stdout_logger = ActiveSupport::Logger.new(STDOUT)
file_logger = ActiveSupport::Logger.new("development.log")
broadcast = ActiveSupport::BroadcastLogger.new(stdout_logger, file_logger)

broadcast.i...

High-level data types with "composed_of"

I recently stumbled upon the Rails feature composed_of. One of our applications dealt with a lot of addresses and they were implemented as 7 separate columns in the DB and Rails models. This seemed like a perfect use case to try out this feature.

TLDR

The feature is still a VERY leaky abstraction. I ran into a lot of ugly edge cases.

It also doesn't solve the question of UI. We like to use simple_form. It's currently not possible to simply write `f...

How to allow testing beforeunload confirmation dialogs with modern ChromeDrivers

Starting with ChromeDriver 127, if your application displays a beforeunload confirmation dialog, ChromeDriver will immediately close it. In consequence, any automated tests which try to interact with unload prompts will fail.

This is because ChromeDriver now follows the W3C WebDriver spec which states that any unload prompts should be closed automatically.
However, this applies only to "HTTP" test sessions, i.e. what you're using by default. The spec also defines that bi-directional test se...