JSON APIs: Default design for common features

When you build a JSON API you need to come up with a style to represent attributes, pagination, errors or including associated objects. Instead of reinventing the wheel, you may reuse successful API designs.

JSON API

JSON:API specifies common features found in most JSON APIs, like pagination, ordering and nested resources. The spec looks very similar to how one would build an API with Rails and uses similar patterns. Even if you don't plan on supporting the whole spec, it can still make sense to know how th...

Converting SVG to other vector formats without Inkscape

If you need to convert an SVG source to PS or EPS, the most common suggestion on the interwebs is to use Inkscape from the commandline.
Inkscape is a fairly resource-heavy tool with lots of dependencies. A great alternative for converting is CairoSVG.

CairoSVG is available on most Linux distros through their package management systems, e.g. apt install cairosvg on Ubuntu.
It has few dependencies (most importantly Python 3 and some related packages, but really not much)...

Web development: Accepting a self-signed certificate in Google Chrome

Working with a self-signed certificate is much easier, when the browser accepts it.

Google Chrome

Warnings from chrome might not be accurate

Even though the certificate is working locally, chrome might still complain that the certificate is not valid and the connection is not secure while blotting out the "https" part of the url.

Accept a specific certificate

  • Go to chrome://settings/certificates
  • Under "Authorities", click "Import"
  • Select the certificate file (.crt)
  • Check "Trust this certificate for identi...

Generating and streaming ZIP archives on the fly

When your Rails application offers downloading a bunch of files as ZIP archive, you basically have two options:

  1. Write a ZIP file to disk and send it as a download to the user.
  2. Generate a ZIP archive on the fly while streaming it in chunks to the user.

This card is about option 2, and it is actually fairly easy to set up.

We are using this to generate ZIP archives with lots of files (500k+) on the fly, and it works like a charm.

Why stream downloads?

Offering downloads of large archives can be cumbersome:

  • It takes time to b...

Rails: Fixing the memory leak / performance issues in prepend_view_path

Recently we detected a memory leak in one of our applications. Hunting it down, we found that the memory leak was located in Rails' #prepend_view_path. It occurs when the instance method prepend_view_path is called in each request, which is a common thing in a multi-tenant application.

On top of leaking memory, it also causes a performance hit, since templates rendered using the prepended view path will not be cached and compiled anew on each request.

This is not a new memory leak. It was [first reported in in 2014](https://github.com/...

Reading the Rails session hash from a Rack middleware

To read the Rails session from a Rack middleware, use env['rack.session']. It's an ActionDispatch::Request::Session object.

class MyMiddlware

  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, body = @app.call(env)
    session = env['rack.session']
    Rails.logger.info("Value of session['foo'] is: " + session['foo'].inspect)
    [status, headers, body]
  end

end

You may not be able to write to the session this way (I haven't tested this).

Fix: esbuild assets are missing after capistrano deploy

Issue: You have an app using jsbundling-rails and esbuild. After deploy, the assets built by esbuild are missing in public/assets.

Solution: Add app/builds to your git repo (by adding a app/builds/.keep file).

Something in sprockets is caching paths and refuses to accept files in "unknown" locations.

JavaScript: Testing whether the browser is online or offline

You can use the code below to check whether the browser can make connections to the current site:

await isOnline() // resolves to true or false

Limitations of navigator.onLine

While you can use the built-in function navigator.onLine (sic), it is only a hint for whether the device can access the Internet.

When navigator.onLine === false you know for certain that the user device has no connection to the Internet. This mea...

Rails: Default HTTP status codes when redirecting

When redirecting you should take care to use the right HTTP status code.

From controllers

When redirecting from a controller, the default status code is 302 Found (aka Moved Temporarily):

red...

Show a JS fiddle in fullscreen

If you have a JS fiddle, you can open it in fullscreen by appending /show to the URL.

Example: https://jsfiddle.net/b275g910/3 => https://jsfiddle.net/b275g910/3/show

Capybara: Most okayest helper to download and inspect files

Testing file download links in an end-to-end test can be painful, especially with Selenium.

The attached download_helpers.rb provides a download_link method for your Capybara tests. It returns a hash describing the download's response:

details = download_link('Download report')
details[:disposition]  # => 'attachment' or 'inline'
details[:filename]     # => 'report.txt'
details[:text]         # => file content as string
details[:content_type] # => 'text/plain'

Features

Compared to [other approaches](...

Project maintenance: four levels of code quality

Code quality can be measured in four levels:

  1. (Working code)
  2. Reliable code (minimum)
  3. Readable code (ok for short-lived code)
  4. Changeable code (standard level)

The code quality of a project directly impacts its maintainability.

Generally you should aim for level 3. If the code will stay for less than a few months, it may stay at level 2. Never go below level 1.

0. Working code

You have implemented that feature and it works. Congrats! You have reached level zero, which means three levels of code quality lie ahead.

First, m...

Why am I getting different results working with SVG files and ImageMagick?

When you are working with SVG files and ImageMagick you can get different results on different machines depending on which additional packages you have installed.

From: http://www.imagemagick.org/script/formats.php

ImageMagick utilizes inkscape if its in your execution path otherwise RSVG. If neither are available, ImageMagick reverts to its internal SVG renderer.

How to get information about a gem (via CLI or at runtime from Ruby)

When you need information about a gem (like version(s) or install path(s)), you can use the gem binary from the command line, or the Gem API inside a ruby process at runtime.

gem binary (in a terminal)

You can get some information about a gem by running gem info <gem name> in your terminal.

Example:

$ gem info irb

*** LOCAL GEMS ***

irb (1.4.1, 1.3.5)
    Author: Keiju ISHITSUKA
    Homepage: https://github.com/ruby/irb
    Licenses: Ruby, BSD-2-Clause
    Installed at (1.4.1): /home/arne/.rbenv/versions/3.0.3/lib/ruby/g...

JavaScript: Working with Query Parameters

tl;dr: Use the URLSearchParams API to make your live easier if you want to get or manipulate query parameters (URL parameters).

URLSearchParams API

The URLSearchParams API is supported in all major browsers except IE 11.

It offers you a bunch of useful methods:

  • URLSearchParams.append() - appends a query parameter
  • URLSearchParams.delete() - deletes the specified query parameter
  • URLSearchParams.get() - returns the value of the specified query parameter
  • `URLSearchP...

Version 5 of the Ruby Redis gem removes Redis.current

Redis.current will be removed without replacement in redis-rb 5.0.
Version 4.6.0 adds deprecation warnings for Redis.current and Redis.current=:

`Redis.current=` is deprecated and will be removed in 5.0.

If your application still uses Redis.current, you can only fix it by no longer using it. Here is how.

Redis.new when you need it

You can easily instantiate a Redis client when you need it.

There is probably already a constant like REDIS_URL that you use to configure Sidekiq or similar. So just use that one.

``...

How to add esbuild to the rails asset pipeline

This are the steps I needed to do to add esbuild to an application that used the vanilla rails asset pipeline with sprockets before.

Preparations

  1. update Sprockets to version 4
  2. add a .nvmrc with your preferred node version (and install it)
  3. add gems jsbundling-rails and foreman to your Gemfile:
    gem 'jsbundling-rails'
    group :development, :test do
      gem 'foreman'
      # ...
    end
    
  4. bundle install
  5. run bin/rails javascript:install:esbuild in a console to prepare esbuild.
  6. run `yarn instal...

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 ...

Carrierwave: How to attach files in tests

Attaching files to a field that is handled by Carrierwave uploaders (or maybe any other attachment solution for Rails) in tests allows different approaches. Here is a short summary of the most common methods.

You might also be interested in this card if you see the following error in your test environment:

CarrierWave::FormNotMultipart:
You tried to assign a String or a Pathname to an uploader, for security reasons, this is not allowed.
If this is a file upload, please check that your upload form is multipart encoded.

Factor...

Using ngrok for exposing your development server to the internet

Sometimes you need to access a dev server running on localhost from another machine that is not part of the same network. Maybe you want to use your phone to test a web page, but are only in a guest WiFi. In the past, we often used some port forwarding or other techniques to expose the service to the internet.

Enter ngrok, a command line tool that gives you an on-the-fly internet...

RSpec: Inferring spec type from file location

RSpec Rails can automatically mix in different behaviors to your tests based on their type tag, for example enabling you to call get and
post in specs with the tag type: :request.

Alternatively you can skip these tags by setting the config config.infer_spec_type_from_file_location! in the spec_helper.rb. This will automatically choose the right type context based on the file location of the test.

For instan...

Caching in Rails < 6.1 may down parts of your application when using public cache control

TL;DR When using Cache-Control on a Rails application, make sure the Vary: Accept header is set.

Proxy caching is a good feature to serve your publicly visible application content faster and reduce load on your servers. It is e.g. available in nginx, but also affects proxies delivered by ISPs.

Unfortunately, there is a little problem in Rails < 6.1 when delivering responses for different MIME-types. Say you have an arbitrary route in your Rails application that is able to respond with regular HTML and JSON. By sending the specific ...

New gem: Rack::SteadyETag

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

By default Rails uses Rack::ETag to generate ETag headers by hashing the response body. In theory this would enable caching for multiple requests to the same resourc...

Elasticsearch: recover from readonly mode

Elasticsearch defaults to go into readonly mode when you run low on disk space (< 95%). You might then see an error like this when you try to write to elastic:

Elasticsearch::Transport::Transport::Errors::Forbidden:
       [403] {"error":{"root_cause":[{"type":"cluster_block_exception","reason":"blocked by: [FORBIDDEN/12/index read-only / allow delete (api)];"}],"type":"cluster_block_exception","reason":"blocked by: [FORBIDDEN/12/index read-only / allow delete (api)];"},"status":403}

Even after freeing up space and restarting elas...