Rails: Composing an ETag from multiple records

Rails offers the fresh_when method to automatically compute an ETag from the given record, array of records or scope of records:

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    fresh_when @user
  end  
  
  
  def index
    @users = User.all.to_a
    fresh_when @users
  end
end

When your view also displays other records (typically associations), those other records should be included in the ETag. You can do so by passing an array of ETaggable objects to fresh_when.

...

Rails: Your index actions probably want strict_loading

By activating strict_loading you force developers to address n+1 queries by preloading all associations used in the index view. Using an association that is not preloaded will raise an ActiveRecord::StrictLoadingViolationError.

I think it's a good default to activate strict_loading in your controllers' #index actions. This way, when a change introduces an n+1 query, you...

Rails: How to test the parsed response body

Testing your responses in Rails allows to parse the body depending on the response MIME type with parsed_body.

get '/posts.json'
response.parsed_body # => [{'id' => 42,  'title' => 'Title'}, ...]

For JSON APIs we often parse the response as symbolized keys with JSON.parse(response.body, symbolize_names: true), which is not supported by parsed_body. For all other cases you might want to drop JSON.parse(response.body) and replace it w...

How to exclusively lock file access in ruby

We will achieve this by creating a block accepting method to optionally create and then lock a .lock File of the underlying accessed file.

Why create a .lock file?

  • The main advantage of creating a .lock file is that #flock might block some operations and require the index node of the file to be consistent. Some operations might change that index node.
  • In some cases it might also be convenient to just read/write the lock file first and update the other file afterwards or vice versa, such that breaking of a process does not...

Project management best practices: Project team responsibilities

In a project team for a bigger project people have several roles:

  • Developer: at makandra
  • Project lead: at makandra
  • Project manager (PM): at makandra, external, or with the customer. In a smaller project this person is also the project lead.
  • Product owner (PO): with the customer

Developer

  • Development
  • Take responsibility for their stories. This includes always gathering all necessary information from the project lead or the PM, communicate blockers, make sure stories are merged, deployed etc.
  • Tell the project lead, if y...

SASS: Reusing styles from other files

SASS has an @extend keyword to inherit styles.

.alert
  color: red
  
  &.-framed
    border: 1px solid red
    padding: 5px
    
  &.-homepage
    @extend .-framed
    border-width: 5px

When compiling, SASS will simply join the selectors. Note how .-homepage is written where .-framed was defined:

...
.alert.-framed, .alert.-homepage {
  border: 1px solid red;
  padding: 5px;
}
.alert.-homepage {
  border-width: 5px;
}

Warning

Unfortunately, this does...

How to enable template coverage support for simplecov

Since Ruby 3.2.0 you can measure coverage support for eval statements and support has been added for the simplecov gem as well.

This allows to track coverage across ruby templates such as haml, erb, ...

Simply set this within simplecov

SimpleCov.start do
  enable_coverage_for_eval
end

SVGO: SVG Optimizer

SVG files are often much larger than necessary, containing comments, metadata, hidden elements etc.

Optimize them with this tool.

Note that for a properly scaling SVG, you need to keep the viewBox attribute. There's an option --disable=removeViewBox for this.

FactoryBot: Passing attributes to associated records using transient attributes

FactoryBot.define do

  factory :parent do
    transient do
      child_name nil
      child_allowed_to_drive false
    end
    
    child do
      association(:child, name: child_name, allowed_to_drive: child_allowed_to_drive)
    end
  end

  factory :child do
    name 'Child'
    allowed_to_drive false
  end

end

# Usage
daughter = FactoryBot.create(:parent, child_name: 'Lisa').child
daughter.name # => 'Lisa'
daughter.allowed_to_drive? # => false

son = FactoryBot.create(:parent, child_name: 'Benedikt', child_allowed_to_drive: tr...

Rails migration: Changing a column type without losing the content

The change_column method for rails migrations support casting with a custom SQL statement. This allows us to change a column type and keep the former content as the new type. This way, we can for example prepare an address number column to hold German address numbers, which can contain letters:

Example (in most cases not a good idea!)

class ChangeAnIntegerColumnToString < ActiveRecord::Migration[6.1]
  def up
    change_column :users, :address_number, 'varchar USING CAST(rating AS varchar)'
  end

  def down
    change_column ...

CSS: CSS Container Queries

Container queries enable you to apply styles to an element based on the size of the element's container. If, for example, a container has less space available in the surrounding context, you can hide certain elements or use smaller fonts. Container queries are an alternative to media queries, which apply styles to elements based on viewport size or other device characteristics.

This feature is now stable across browsers.

Warning

This feature landed in browsers in the beginning of 2023. According to our support policy this will bec...

Creating a sample video with ffmpeg

If you need a sample video with certain properties for a test you can create one using ffmpeg.
You might want a very low bitrate file to speed up processing in your test. (e.g. you only care about the length, then you can create a video with a very low resolution and framerate)

Create a 21s video with 1fps and 10x10 resolution:
ffmpeg -t 21 -s 10x10 -r 1 -f rawvideo -pix_fmt rgb24 -i /dev/zero sample_21_seconds.mp4

Option Explanation
-t 21 set the length to 21s
-s 10x10 set the resolution the 10 by 10 p...

Shorthand function properties in ES6

Here is an ES5 object literal with two string properties and a function property:

let user = { 
  firstName: 'Max',
  lastName: 'Muster',
  fullName: function() { return this.firstName + ' ' + this.lastName }
}

user.fullName() // => 'Max Muster'

In ES6 we can define a function property using the following shorthand syntax:

let user = { 
  firstName: 'Max',
  lastName: 'Muster',
  fullName() { return this.firstName + ' ' + this.lastName }
}

user.fullName() // => 'Max Muster'

We can also define a gette...

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

Issue Checklist Template

This is a checklist I use to work on issues. For this purpose I extracted several cards related to the makandra process and ported them into a check list and refined that over time a little bit.

This task list is divided by the Gate keeping process in the following steps:

1. Starting a new feature
2. Working on the issue
3. Finishing a feature
4. After Review

Here are some ti...

Manipulate color with Sass functions

Sass comes with many built-in functions to manipulate color. Some of the more interesting functions include:

adjust-hue($color, $degrees)

:Changes the hue of a color.

lighten($color, $amount)

:Makes a color lighter.

darken($color, $amount)

:Makes a color darker.

saturate($color, $amount)

:Makes a color more saturated.

desaturate($color, $amount)

:Makes a color less saturated.

grayscale($color)

:Converts a color to grayscale.

complement($color)

:Return...

Jasmine: Testing AJAX calls that manipulate the DOM

Here is a Javascript function reloadUsers() that fetches a HTML snippet from the server using AJAX and replaces the current .users container in the DOM:

window.reloadUsers = ->
  $.get('/users').then (html) ->
    $('.users').html(html)

Testing this simple function poses a number of challenges:

  • It only works if there is a <div class="users">...</div> container in the current DOM. Obviously the Jasmine spec runner has no such container.
  • The code requests /users and we want to prevent network interaction in our uni...

Rails: Using database default values for boolean attributes

In the past we validate and set default values for boolean attributes in Rails and not the database itself.

Reasons for this:

  • Older Rails didn't support database defaults when creating new records
  • Application logic is "hidden" in the database

An alternative approach, which currently reflects more the general opinion of the Rails upstream on constraints in the database, is adding default values in the schema of the database itself. We also ...

Configuring RubyGems to not install documentation by default

When installing gems, a lot of time is spent building locally installed documentation that you probably never use.

We recommend you disable documentation generation for gem install by default.
Note that Bundler won't install documentation, so this advice applies only when installing gems manually.

If you don't already have it, create a ~/.gemrc file. The gemrc is a Yaml file, so add the following line to add default switches to the gem command.

gem: --no-document

(If you do n...

How to turn images into inline attachments in emails

Not all email clients support external images in all situations, e.g. an image within a link. In some cases, a viable workaround is to turn your images into inline attachments.

Note

Rails provides a simple mechanism to achieve this:

This documentation makes it look like you have to care about these attachments in two places. You have to create the attachment in t...

Carrierwave: Custom file validations inside custom Uploaders

Carrierwave's BaseUploader can have some validations that you can use by overriding a certain method, which's expected name is hard coded. A popular example is extension_allowlist, which returns an array of strings and let's you only upload files that have a filename with an extension that matches an entry in that array. Another useful validation can be size_range, which gives you a little bit of control over how your storage gets polluted.

This is often good enough, but some times you need to validate special cases.

Validations t...

Rails: Encrypting your database information using Active Record Encryption

Since Rails 7 you are able to encrypt database information with Active Record. Using Active Record Encryption will store an attribute as string in the database. And uses JSON for serializing the encrypted attribute.

Example:

  • p: Payload
  • h: Headers
  • iv: Initialization Vector
  • at: Authentication Tag
{ "p": "n7J0/ol+a7DRMeaE", "h": { "iv": "DXZMDWUKfp3bg/Yu", "at": "X1/YjMHbHD4talgF9dt61A=="} }

Note this before encrypting attributes with Active Record:
...

How to view a file from another branch

Just run git show branch:file. Examples:

git show HEAD~:bin/command
git show origin/master:../lib/version.rb

git: find the version of a gem that releases a certain commit

Sometimes I ran across a GitHub merge request of a gem where it was not completely obvious in which version the change was released. This might be the case for a bugfix PR that you want to add to your project.

Git can help you to find the next git tag that was set in the branch. This usually has the name of the version in it (as the rake release task automatically creates a git tag during release).

git name-rev --tags <commit ref>

Note

The more commonly used git describe command will return the last tag before a c...