Guide to localizing a Rails application

Localizing a non-trivial application can be a huge undertaking. This card will give you an overview over the many components that are affected.

When you are asked to give an estimate for the effort involved, go through the list below and check which points are covered by your requirements. Work with a developer who has done a full-app localization before and assign an hour estimate to each of these points.

Static text

  • Static strings and template text in app must be translated: Screens, mailer templates, PDF templates, helpe...

Case sensitivity in PostgreSQL

PostgreSQL, unlike MySQL, treats strings as case sensitive in all circumstances.

This includes

  • comparison with = and LIKE
  • collision detection in unique indexes

Usually this is fine, but some strings (like emails and usernames) should typically be treated as case insensitive.

There are a few workarounds available:

  • use the citext extension (not recommended)
  • use ILIKE instead of LIKE
  • use Postgres' lower() function
  • add an index on lower(email)

Probably th...

PostgreSQL: How to use with_advisory_lock to prevent race conditions

If you want to prevent that two processes run some code at the same time you can use the gem with_advisory_lock.

What happens

  1. The thread will wait indefinitely until the lock is acquired.
  2. While inside the block, you will exclusively own the advisory lock.
  3. The lock will be released after your block ends, even if an exception is raised in the block.

This is usually required if there is no suitable database row to lock on.

Example

You want to generate a...

Allow capybara to click on labels instead of inputs for checkboxes

Within Capybara you most certainly use the #check- and #uncheck-method to (un)check checkboxes.
But there's one problem, if you want to test a custom styled checkbox, which hides its <input>-Tag:

  • The methods cannot (un)check checkboxes without an visible <input>.
  • The error message will be something like: Unable to find visible checkbox "Some label" that is not disabled

Solution 1

Use the keyword argument allow_label_click: true within the method call.
So instead of check('Some label'), use `check('Some label', allow...

Fast rubocop autocorrection alias

The rubocop binary has a few interesting flags:

  • rubocop (using the --parallel default ) scans the current repository for linting issues while using multiple CPU cores
  • rubocop -a (or --autocorrect) safely corrects most offenses while doing a sequential scan
  • rubocop -A (or --autocorrect-all) also tries to correct unsafe suggestions

Autocorrection takes significantly longer on large projects because of the sequential nature.
To speed things up, you can use the following alias. It first checks in parallel if any files...

The numericality validator does not care about your BigDecimal precision

Looking at the source code of the validates_numericality_of validator, it becomes clear that it converts the attribute in question to either an integer or float:

if configuration[:only_integer]
  unless raw_value.to_s =~ /\A[+-]?\d+\Z/
    record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
    next
  end
  raw_value = raw_value.to_i
else
 begin
    raw_value = Kernel.Float(raw_val...

Git: Advisory for cherry-picks to production branches

We often have a separate production branch that lags a bit behind the more cutting edge main branch. Sometimes you want to move some, but not all commits from main to production. This can be done with a git cherry-pick.

However, this may lead to considerable pain later, since git does not understand the commits are actually "the same". Hazards are unnecessary and hard to resolve conflicts as well as incorrect auto-merges.

In order to avoid this, always merge the production branch back to the main after the cherry-pick. Even t...

Be careful when using buttons without a "type" attribute

Be careful when using buttons without a type attribute, since browsers will consider them the default submit button of a form.

Suppose you have this form:

<form action="/save">
  <input type="text" />
  <button onclick="alert('Alert!')">Alert</button>
  <button type="submit">Save</button>
</form>

If you press the enter key inside in the text input, browsers will trigger the first button and show the alert.

To fix this, add a type="button" attribute to the first button.

We have deprecated Rack::SteadyETag

Rack::SteadyETag was a Rack middleware that generates the same default ETag for responses that only differ in XOR-masked CSRF tokens or CSP nonces.

We have deprecated Rack::SteadyETag. We instead recommend reconfiguring your Rails app so two requests to the same resource produce the same HTML for a given user.

Rails: Fixing ETags that never match

Every Rails response has a default ETag header. In theory this would enable caching for multiple requests to the same resource. Unfortunately the default ETags produced by Rails are effectively random, meaning they can never match a future request.

Understanding ETags

When your Rails app responds with ETag headers, future requests to the same URL can be answered with an empty response if the underlying content ha...

Operators "in" and "of" are very inconsistent between CoffeeScript and JavaScript

CoffeeScript and JavaScript (ECMAScript) both have operators in and of. Each language use them for more than one purpose. There is not a single case where the same operator can be used for the same purpose in both languages.

Check if an object (or its prototype) has a property

CoffeeScript

var hasFoo = 'foo' of object

JavaScript

var hasFoo = 'foo' in object;

Iterate through all properties of an object
================================...

Popular mistakes when using nested forms

Here are some popular mistakes when using nested forms:

  • You are using fields_for instead of form.fields_for.
  • You forgot to use accepts_nested_attributes in the containing model. Rails won't complain, but nothing will work. In particular, nested_form.object will be nil.
  • The :reject_if option lambda in your accepts_nested_attributes call is defined incorrectly. Raise the attributes hash given to your :reject_if lambda to see if it looks like you expect.
  • If you are nesting forms into nested forms, each model involved ne...

Test Driven Development with Cucumber and RSpec

Test Driven Development is the paradigm to "first write a test, then implement the code". This guide shows how to apply it in practice, with Cucumber and RSpec as integration and unit test frameworks.

Hierarchical approach

I like to view TDD as a process with three levels:

  • Cucumber level (integration test)
  • Code level
  • RSpec level (unit test)

During development you are frequently changing levels. It may look something like this:

Integration _                    ___
Code         \____    _____   __/   \__ ...
Spec            ...

Modern CSS supports individual transform properties

tl;dr

Individual transform properties are great because they allow you to write more readable and maintainable CSS, especially when applying multiple transformations and/or when animating transforms.

For ages, CSS transforms had to be defined using the transform property. For a single transformation, this was something like transform: scale(1.5), and multiple transformations could be applied by chaining them.

.example {
  transform: scale(1.5) rotate(45deg) translateY(-50%);
}

All modern browsers (Chrome & Edge ...

ASDF: A Version Manager To Rule Them All

tl;dr

asdf allows you to manage multiple runtime versions with a single CLI tool and is backwards compatible by supporting existing config files, like e.g. .nvmrc or .ruby-version.

Getting Started

  1. Disable rbenv
    1.1 Delete or comment out source /home/$user/.rbenvrc in ~/.profile
    1.2 Delete or comment our eval "$(rbenv init -)" in ~/.bashrcor~/.zshrc`
    1.3 To take effect you may have to restart your shell or log out and log in again from your current linux session
  2. Install asdf by following the official [...

Jasmine: Preventing unhandled promise rejections from failing your test

You have an async function that rejects:

async function failingFunction() {
  throw new Error("Something went wrong")
}

When you call that function in a test, your test will fail:

it('has a test', function() {
  failingFunction() // this will fail your test
})

The failure message will look like this:

Unhandled promise rejection: Error: Something went wrong

You can fix this by expecting the state of the returned promise:

it('has a test', async function() {
  await expectAsync(failingFunction()).toBeRej...

Decide whether cronjobs should run on one or all servers

Understanding your type of cronjob

Some cronjobs must only run on a single server. E.g. when you run nightly batch operations on the database, it should probably run on a single server. Running it on multiple servers would likely result in deadlocks or corrupt data.

Some cronjobs must always run on all servers. E.g. starting a sidekiq process on reboot.

Configuring whenever

If not configured otherwise, cronjobs defined in whenever's `s...

Take care of indentation and blank lines when using .erb for plain text emails

Building plain text emails with an .erb template doesn't allow you to indent code like you normally do in HTML mails.

DON'T

<%= 'foo' if bar %>

"\n" if bar is false

"foo\n" if bar is true


<%= nil %>

"\n"


<% if true %>
  <%= 'foo' %>
<% end %>  

" foo"


<%= 'foo' %>

<%= 'bar' %>

"foo\n\nbar\n"

DO

Write unindented code to get the expected result.

<% if bar %>
<%= 'bar' %>
<% end %>
<%= 'foo' %>
<%= 'bar' %> 
  • Use [Form Models](https://github.com/makandra...

RSpec: run a single spec (Example or ExampleGroup)

RSpec allows you to mark a single Example/ExampleGroup so that only this will be run. This is very useful when using a test runner like guard.

Add the following config to spec/spec_helper.rb:

RSpec.configure do |config|
  # These two settings work together to allow you to limit a spec run
  # to individual examples or groups you care about by tagging them with
  # `:focus` metadata. When nothing is tagged with `:focus`, all examples
  # get run.
  config.filter_run_including :focus => true
  config.run_all_when_everything_filtere...