Local development with SSL and Puma

Sometimes the need arises for SSL in local development. We have guides for different webservers, this one is for puma.

  1. make sure mkcert is installed

  2. create an SSL certificate for localhost with mkcert:

$ mkcert-v1.4.4-linux-amd64 localhost
Created a new local CA 💥
...
  1. use the certificate in the Puma config config/puma.rb:
localhost_key = "#{File.join('localhos...

Yarn: Use yarn-deduplicate to cleanup your yarn.lock

Note

Use yarn dedupe in Yarn v2+: https://yarnpkg.com/cli/dedupe

This package only works with Yarn v1. Yarn v2 supports package deduplication natively!

A duplicate package is when two dependencies are resolved to a different version, even when a single version matches the range specified in the dependencies. See the Deduplication strategies section for a few examples.

Yarn is stupid, so it can happen that there are several version of the same package in your bundle, although one would fulf...

Heads up: pg_restore --clean keeps existing tables

When restoring a PostgreSQL dump using pg_restore, you usually add the --clean flag to remove any existing data from tables.

Note that this only removes data from tables that are part of the dump and will not remove any extra tables. You need to do that yourself.

Common mistakes when storing file uploads with Rails

1. Saving files to a directory that is not shared between deploys or servers

If you save your uploads to a made up directory like "RAILS_ROOT/uploads", this directory goes away after every deploy (since every release gets a new). Also this directory is not shared between multiple application servers, so your uploads are randomly saved to one local filesystem or another. Fixing this afterwards is a lot of fun.

Only two folders are, by default, shared between our application servers and deployments: "RAILS_ROOT/storage" and `"RAILS...

Rails: Do not load frameworks you don't need

Rails is split into a large number of (sub-) frameworks.

The most important and central of those are

  • activesupport (extends the Ruby standard library)
  • activerecord / activemodel (ORM for Rails)
  • actionview / actionpack (controller / views)
  • actionmailer (sends mails)

However, there are also some more situational frameworks included, such as

  • actioncable (real time communications using websockets)
  • actionmailbox (receives mails)
  • actiontext (support for WYSIWYG text editor)
  • activejob (background jobs)
  • activestorage (file uplo...

CSS: :is() pseudo selector

tl;dr

The :is() pseudo selector - specificity of its most specific argument - matches against a comma-separated list of selectors.

Example

Compound selectors like ...

.datepicker .prev, .datepicker .next, .datepicker .switch
  padding-bottom: 1rem

ul li, ol li
  list-style-type: none

can be simplified by using the :is() pseudo selector ...

.datepicker :is(.prev, .next, .switch)
  padding-bottom: 1rem

:is(ul, ol) li
  list-style-type: none

Hint

The specificity of :is() is equals t...

CSS: :where() pseudo selector

tl;dr

The :where() pseudo selector - zero specificity - matches against a comma-separated list of selectors.

Example

Compound selectors like ...

.datepicker .prev, .datepicker .next, .datepicker .switch
  padding-bottom: 1rem

ul li, ol li
  list-style-type: none

can be simplified by using the :where() pseudo selector ...

.datepicker :where(.prev, .next, .switch)
  padding-bottom: 1rem

:where(ul, ol) li
  list-style-type: none

Hint

The specificity of :where() is always zero!

I...

Don't sum up columns with + in a SQL query if NULL-values can be present.

Don't sum up columns with + in a sql-query if NULL-Values can be present.

MySQL and PostgreSQL cannot sum up NULL values with the + value. The sum value will be NULL.

MySQL:

mysql> select 1 + 2 + 3;
+-----------+
| 1 + 2 + 3 |
+-----------+
|         6 |
+-----------+
1 row in set (0,00 sec)

mysql> select 1 + NULL + 3;
+--------------+
| 1 + NULL + 3 |
+--------------+
|         NULL |
+--------------+
1 row in set (0,00 sec)

Postgres:


test_database=# select 1 + 2 + 3;

 ?column? 
----------
        6
(1 row)

t...

Rails and Postgres: How to test if your index is used as expected

This is a small example on how you can check if your Postgres index can be used by a specific query in you Rails application. For more complex execution plans it might still be a good idea to use the same path of proof.

1. Identify the query your application produces

query = User.order(:last_name, :created_at).to_sql
puts query
# => SELECT "users".* FROM "users" ORDER BY "users"."last_name" ASC, "users"."created_at" ASC

2. Add an index in your migration and migrate

add_index :users, [:last_name, :created_at]...

How to use Ubuntu in English, but still show German formats

If you want to have an English Ubuntu UI, but still see dates, money amounts, paper formats, etc. in German formats, you can fine-tune your /etc/default/locale like this:

LANG="en_US.UTF-8"
LC_CTYPE="de_DE.UTF-8"
LC_NUMERIC="de_DE.UTF-8"
LC_TIME="de_DE.UTF-8"
LC_COLLATE="de_DE.UTF-8"
LC_MONETARY="de_DE.UTF-8"
LC_PAPER="de_DE.UTF-8"
LC_NAME="de_DE.UTF-8"
LC_ADDRESS="de_DE.UTF-8"
LC_TELEPHONE="de_DE.UTF-8"
LC_MEASUREMENT="de_DE.UTF-8"
LC_IDENTIFICATION="de_DE.UTF-8"

Make sure you have both en...

Rails: How to check if a certain validation failed

If validations failed for a record, and you want to find out if a specific validation failed, you can leverage ActiveModel's error objects.
You rarely need this in application code (you usually just want to print error messages), but it can be useful when writing tests.

As an example, consider the following model which uses two validations on the email attribute.

class User < ApplicationRecord
  validates :email, presence: true, uniqueness: true
end

Accessing errors

Let's assume we have a blank user:

user = Us...

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

Showing a custom maintenance page while deploying

Note

The maintenance mode is enabled on all application server as soon as the file /public/system/maintenance.html is present.

Installation

Add this line to your application's Gemfile:

 gem 'capistrano', '~> 3.0'
 gem 'capistrano-maintenance', '~> 1.0'

Add this line to you application's Capfile:

require 'capistrano/maintenance'

Enable task

Present a maintenance page to visitors. Disables your application's web interface by writing a #{maintenance_basename}.html file to each web server. The servers m...

Cucumber pitfall: "Around" does not apply to your "Background" steps

Around will not happen until after a feature's Background has been processed. Use Before and After to avoid that.

Details

Consider this Cucumber feature file:

Feature: Something that needs to be tested

  Background:
    Given a user
      And I sign in        
    
  Scenario: Sign out
    When I sign out
    Then I should see "Signed out"
  
  Scenario: Something else
    # ...

Now, assume you have these step definitions:

Around do
  puts "** Around: before yield"

...

Valuable Chrome DevTools Shortcuts

In the DevTools settings, there's a "Shortcuts" section. Found these keyboard shortcuts there:

General

ESC

Toggle drawer

CTRL + ~ or CTRL + `

Show console in drawer

Styles

SHIFT + up/down

Change number by 10

CTRL + up/down

Change number by 100

Elements

H

Toggle "visibility:hidden!important" (useful when debugging page repaint times)

CTRL + hover above element in the DOM list

Don't show the yellow dimensions tooltip (useful when the tooltip covers just the area you need to see).

Drag...

A short overview of common design patterns implemented within Rails

The linked content includes a few design patterns implemented with Ruby on Rails.

What is the card indented to achieve?

  • You can use the pattern names for code reviews, so all parties know with only a few words which change is requested. Example: "Please use a form object here"
  • You can learn about new code patterns
  • You should read the sections "Advantages of using design patterns" and "Disadvantages of using design patterns in a wrong way", since design patterns do not replace good code

Included Design Patterns: Service, Value objec...

makandra tech survey - results

These are the results of the "personal tech stack survey". I've included only the most popular mentions, maybe it can help you find one or two useful tools for your own usage.

Desktop environment

pie title Desktop environment
    "Gnome" : 16
    "i3": 2
    "sway": 2
    "awesome": 1
    "bspwm": 1
    "mate": 1
    "xfce": 1

Gnome dominates (unsuprising, it's the Ubuntu default), but quite a few people use tiling window managers, most popular i3 and the mostly i3-compatible [sway](https://swaywm....

Fix for mysql2 error "Incorrect MySQL client library version! This gem was compiled for x.x.x but the client library is y.y.y."

This should be fixed in the latest LTS-branches of our mysql2 fork, 0.2.x-lts and 0.3.x-lts.

Use

gem 'mysql2', git: 'https://github.com/makandra/mysql2', branch: '0.2.x-lts' # for Rails 2.x
gem 'mysql2', git: 'https://github.com/makandra/mysql2', branch: '0.3.x-lts' # for Rails 3.x

in your Gemfile, and do a

bundle update mysql2

Background

mysql2 used to check that the client library used at runtime actually matches the one it was compiled against. However, at least on Ubunt...

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

Fixing flaky E2E tests

An end-to-end test (E2E test) is a script that remote-controls a web browser with tools like Selenium WebDriver. This card shows basic techniques for fixing a flaky E2E test suite that sometimes passes and sometimes fails.

Although many examples in this card use Ruby, Cucumber and Selenium, the techniques are applicable to all languages and testing tools.

Why tests are flaky

Your tests probably look like this:

When I click on A
And I click on B
And I click on C
Then I should see effects of C

A test like this works fine...

Use a global .gitignore file to ignore stuff from your machine

Sometimes you want git to ignore certain files that appear on your machine. You can do this in 3 ways:

  • Per project, in the project's .gitignore file
  • Per project, in a local exclude file
  • Globally on your machine

Downsides of per-project .gitignore entries

While it might be tempting to set it per project (other devs might benefit from it), you

  • need to do it each time for every project
  • "pollute" a project's .gitignore file with stuff...

Running "bundle update" without arguments might break your application

Calling bundle update (without arguments) updates all your gems at once. Given that many gems don't care about stable APIs, this might break your application in a million ways.

To stay sane, update your gems using the applicable way below:

Projects in active development

Update the entire bundle regularily (e.g. once a week). This ensures that your libraries are up-to-date while it's easy to spot major version bumps which may break the app.

Projects that have not been updated in a while

  1. [Update a single gem conservatively](htt...