Rails: Rest API post-mortem analysis

This is a personal post-mortem analysis of a project that was mainly build to provide a REST API to mobile clients.

For the API backend we used the following components:

  • Active Model Serializer (AMS) to serializer our Active Record models to JSON.
  • JSON Schema to test the responses of our server.
  • SwaggerUI to document the API.

It worked

The concept worked really good. Here are two points that were extraordinary compared to normal Rails project with many UI components:

  • Having a Rails application, that has no UI components (only...

Fixing "identify: not authorized"

Ubuntu has decided to disable PDF processing because ImageMagick and the underlying Ghostscript had several security issues.

When your Ghostscript is up to date (and receiving updates regularly), you can safely reactivate PDF processing on your computer like this:

  1. Open /etc/ImageMagick-6/policy.xml (requires sudo)
    • For older versions of Ubuntu (or possibly ImageMagick), the path is /etc/ImageMagick/policy.xml
  2. Remove/Comment lines after <!-- disable ghostscript format types -->

If you need ...

Setting up Tomcat to use an existing OpenSSL certificate

This is for those who already own an SSL certificate (e.g. using it in the Apache HTTP Server) and need to feed it to a Tomcat application. The main issue is that you need to convert your OpenSSL certificate for Java to use it in its keystore.

Create a keystore

This is the place where the application usually gets its keys from. The keytool is able to read certificate files but does not understand private key files which you need to use y...

How to downgrade Google Chrome in Ubuntu

If you're experiencing problems with your Google Chrome installation after an update, it might help downgrading Chrome to check if the problem disappears. Keep in mind though that running outdated software, especially web browsers, is in most cases not a good idea. Please verify periodically if you still need to run the old version or if an even more recently updated version fixes the problems introduced in your version.

Here's how to get old versions of Chrome for your Ubuntu installation:

First, go to [UbuntuUpdates](https://www.ubuntuup...

Mock the browser time or time zone in Selenium features

In Selenium features the server and client are running in separate processes. Therefore, when mocking time with a tool like Timecop, the browser controlled by Selenium will still see the unmocked system time.

timemachine.js allows you to mock the client's time by monkey-patching into Javascript core classes. We use timemachine.js in combination with the Timecop gem to synchronize the local browser time to the ...

Debugging your Webpack build time with Speed Measure Plugin

If your Webpack build is slow, you can use the Speed Measure Plugin for Webpack to figure out where time is being spent.
Note that at time of writing, Webpack 5 seems unsupported. It works on Webpack 4, though.

Wire it into your application as described in the library's documentation:

  1. Hook into your environment file, e.g. config/webpack/development.js and instead of exporting your Webpack config,...

Chromedriver: Connect local chromedriver with docker

Debugging your integration tests, that run a headless Chrome inside a docker image, is tricky.

In many cases you can connect your Chrome to a remote docker container like docker-selenium, which should be the preferred way when you try to inspect a page within your integration test.

Otherwise you might be able to start your docker container with --net=host and access your local chromedriver in the host address space host.docker.internal.

If both options above don't work for you here is a...

MySQL: Disable query cache for database profiling

If you want to see how long your database queries actually take, you need to disable MySQL's query cache. This can be done globally by logging into a database console, run

SET GLOBAL query_cache_type=OFF;

and restart your rails server.

You can also disable the cache on a per query basis by saying

SELECT SQL_NO_CACHE * FROM ...

You also probably want to disable Rails internal (per-request) cache. For this, wrap your code with a call to ActiveRecord::Base.uncached. For example, as an around_filter:

d...

Change / Update SSL certificate for Amazon Elastic Load Balancer

There is a new card about how to do this with the new AWS Command Line Interface


At first you need the IAM Cli Tools.
-------------------------------------------------------------------------------------------------------------...

esbuild: Make your Rails application show build errors

Building application assets with esbuild is the new way to do it, and it's great, especially in combination with Sprockets (or Propshaft on Rails 7).
You might be missing some convenience features, though.

Here we cover one specific issue:
Once you have started your development Rails server and esbuild with the --watch option (if you used jsbundling-rails to set up, you probably use bin/dev), esbuild will recompile your assets upon change, but build errors will only be printed to the terminal. Your application won't complain about them ...

Missing certificates for rubygems and bundler in Ruby 1.8.7

Using Ruby 1.8.7 you will not be able to use the maximum versions Rubygems 1.8.30 and Bundler 1.17.3 with https://rubygems.org/ anymore. This is a result of a server certificate on December 5th, 2020. The resulting errors will look like following:

  • TypeError: can't modify frozen object
  • Could not verify the SSL certificate for https://rubygems.org/*
  • Bundler::Fetcher::CertificateFailureError: Could not verify the SSL certificate for https://index.rubygems.org/versions.
  • `Error fetching data: hostname was not m...

Heads up: Deployment with newly generated SSH key (using ED25519) might fail

If you use a newer SSH key generated with the ED25519 algorithm instead of RSA (see Create a new SSH key pair), the deployment with Capistrano may fail with the following message:

The deploy has failed with an error: unsupported key type `ssh-ed25519'
net-ssh requires the following gems for ed25519 support:
 * ed25519 (>= 1.2, < 2.0)
 * bcrypt_pbkdf (>= 1.0, < 2.0)
See https://github.com/net-ssh/net-ssh/issues/565 for more information
Gem::LoadError : "ed25519 i...

jQuery and cross domain AJAX requests

When making cross-domain AJAX requests with jQuery (using CORS or xdomain or similar), you will run into issues with HTTP headers:

  • jQuery will not set the X-Requested-With header. On your server, requests will not look like AJAX requests (request.xhr? will be false).
  • jquery-ujs will not set CSRF headers.

This is by design and improves secu...

Better HTML forms: use type, inputmode, enterkeyhint and autocomplete

Web forms can be made much more usable with a few HTML attributes. Short summary:

  • type: Tells browsers about the input data type. Mobile browsers will select a virtual keyboard based on this value. Some browsers will add simple validation, e.g. for type email.
  • inputmode: Direct hint about the virtual keyboard to use. Inferred from type, but can be very handy when ...

Read your mail in networks that forbid e-mail traffic

If you are connected with a network that forbids e-mail traffic but allows SSH, you can tunnel your e-mail connection through a trusted, intermediary server:

 sudo ssh -i /home/local-user/.ssh/local-user.key -L 143:mail-server.tld:143 remote-user@trusted-server.tld

Explanation for the command above:

| sudo | Secure ports may only be forwarded by root |
| ssh -i /home/local-user/.ssh/local-user.key | Private half of your SSH key for the trusted server |
| -L 143:mail-server.tld:143 | Forward connections to port 143 on your mail s...

Carrierwave: How to migrate to another folder structure

A flat folder structure can be cool if you have only a few folders but can be painful for huge amounts. We recently had this issue in a project with more than 100.000 attachments, where we used a structure like this /attachments/123456789/file.pdf.

Even the ls command lasted several minutes to show us the content of the attachments folder.

So we decided to use a more hierarchical structure with a limited maximum of folder per layer. Here are a few tips how to migrate your files to their new...

A saner alternative to SimpleForm's :grouped_select input type

SimpleForm is a great approach to simplifying your forms, and it comes with lots of well-defined input types. However, the :grouped_select type seems to be overly complicated for most use cases.

Example

Consider this example, from the documentation:

form.input :country_id, collection: @continents,
  as: :grouped_select, group_method: :countries

While that looks easy enough at a first glance, look closer. The example passes @continents for a country_id.\
SimpleForm actua...

Debugging Capistrano

Capistrano 3 has a doctor task that will print information about

  • Environment: Ruby, Rubygems and Bundler versions
  • List of Capistrano gems and whether an update is available
  • All config variables and their values
  • Capistrano server config
$ bundle exec cap staging doctor

Debug flaky tests with an Unpoly observeDelay

The problem

Unpoly's [up-observe], [up-autosubmit] and [up-validate] as well as their programmatic variants up.observe() and up.autosubmit() are a nightmare for integration tests.

Tests are usually much faster than the configured up.form.config.observeDelay. Therefore, it may happen that you already entered something into the next field before unpoly updates that field with a server response, discarding your changes.

The steps I wait for active ajax requests to complete (if configured) and capybara-lockstep can catch some ...

Carrierwave: Using a nested directory structure for file system performance

When storing files for lots of records in the server's file system, Carrierwave's default store_dir approach may cause issues, because some directories will hold too many entries.

The default storage directory from the Carrierwave templates looks like so:

class ExampleUploader < CarrierWave::Uploader::Base
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end
end

If you store files for 500k records, that store_dir's parent directory will have 500k sub-directories which will cause some...

How to fix "unknown role" errors in Capistrano recipes

When you have a complex recipe setup with multistage deployment you may run into this error:

`role_list_from': unknown role `something' (ArgumentError)

Consider this task definition:

namespace :foo do
  task :bar, :roles => :something do
    # do crazy stuff
  end
end

Whenever we call foo.bar in our recipe, Capistrano will fail if you deploy to a stage where none of the servers has the role the error complains about, "something" in this case.

However, you can [hack around it](http://groups.google.com/group/ca...

Capistrano 3: How to deploy when a firewall blocks your git repo

Sometimes, through some firewall or proxy misconfiguration, you might have to deploy to a server that cannot access the git repository.

Solution 1: HTTP Proxy (this is the preferred fix)

SSH can be tunneled over an HTTP Proxy. For example, when the repo is on github, use this:

  1. Install socat

  2. Add a ~/.ssh/config on the target server(s) with permission 0600 and this content:

    Host github.com ssh.github.com
      User git
      Hostname ssh.github.com
      Port 443
      ProxyCommand socat - PROXY:<your proxyhost>:%h:%p,...
    

Workflow: How to use a key management service to encrypt passwords in the database

This is an extract from the linked article. It shows an approach on how to implement encrypted passwords with the AWS Key Management Service (KMS).

For most applications it's enough to use a hashed password with a salt (e.g. the gem devise defaults to this).

Upon password creation

  1. Generate hash as hash of password + salt.

  2. Encrypt the hash with a public key from KMS (you can store the public key in your server code).

  3. In your database sto...

Loading dumps via SSH, unpacking and sourcing them, all with a progress bar

Here is a hacky way to load dumps directly from the source server, without fully copying them over and extracting them first.

It may break horribly for you. This is the dark side of the force.

  1. Install pipe viewer, if you don't have it already: sudo apt-get install pv
  2. Know the location of the dump file on the remote server. We'll use /mnt/dumps/my_project.dump.bz2 in the example below.
  3. Find out the size of the (bzipped) file in by...