How to write complex migrations in Rails

Rails gives you migrations to change your database schema with simple commands like add_column or update.
Unfortunately these commands are simply not expressive enough to handle complex cases.

This card outlines three different techniques you can use to describe nontrivial migrations in Rails / ActiveRecord.

Note that the techniques below should serve you well for tables with many thousand rows. Once your database tables grows to millions of rows, migration performance becomes an iss...

Gatekeeping: Guide for developer

If your project manager wants to do gatekeeping on a project, as a developer you need to follow the following guidelines (e.g. by using something like this issue checklist template).

In order to reduce the number of rejects we get from clients, we want to review all code written before it goes to the staging server.


Note: This process is tailored to our specific needs and tools at makandra. While it will certainly not apply to all (especially larger tea...

How to use html_safe correctly

By default, Rails views escape HTML in any strings you insert. If you want to insert HTML verbatim, you need to call #html_safe. However, #html_safe does not "unescape" a string. It merely marks a string as safe for unescaped insertion.

How html_safe works

Calling html_safe on a String returns a new object that looks and acts like a String, but actually is a ActiveSupport::SafeBuffer:

"foo".length
# => 3
"foo".class
# => String

"foo".html_safe.length
# => 3
"foo".html_safe.class
# => ActiveSupport::S...

Your browser might silently change setTimeout(f, 0) to setTimeout(f, 4)

When you're nesting setTimeout(f, 0) calls, your browser will silently increase the delay to 5 milliseconds after the fourth level of nesting.

This is called "timeout clamping" and defined in the HTML spec:

If nesting level is greater than 5, and timeout is less than 4, then set timeout to 4.

Timeouts are clamped harder in background tabs

On a similar note, all major browsers have implemented throttling rules for setInterval and setTimeout calls from tabs...

Adding Jasmine JavaScript specs to a Webpack(er) project

The goal is to get Jasmine specs running in a Rails project using Webpacker, with the browser based test runner. Should be easily adaptable to a pure Webpack setup.

Image

Step 1: Install Jasmine

yarn add jasmine-core

Step 2: Add two separate packs

Since we do not want to mix Jasmine into our regular Javascript, we will create two additional packs. The first only contains Jasmine and the test runner. The second will contain our normal application code and the specs themselves.

We cannot...

RSpec: Composing a custom matcher from existing matchers

When you find similar groups of expect calls in your tests, you can improve readability by extracting the group into its own matcher. RSpec makes this easy by allowing matchers to call other matchers.

Example

The following test checks that two variables foo and bar (1) have no lowercase characters and (2) end with an exclamation mark:

expect(foo).to_not match(/[a-z]/)
expect(foo).to end_with('!')

expect(bar).to_not match(/[a-z]/)
expect(bar).to end_with('!')

We can extract the repeated matcher chains into a custom m...

How to: Benchmark an Active Record query with a Ruby script

Recently I needed to benchmark an Active Record query for performance measurements. I wrote a small script that runs each query to benchmark 100 times and calculates the 95th percentile.

Note: The script requires sudo permissions to drop RAM cache of PostgreSQL. Due to the number of iterations it was impractical to enter my user password that often. And I temporary edited my /etc/sudoers to not ask for the sudo password with johndoe ALL=(ALL) NOPASSWD: ALL.

# Run this script with e.g. `rails ru...

Using the ActiveSupport::BroadcastLogger

The ActiveSupport::BroadcastLogger allows you to log to multiple sinks. You know this behavior from from the rails server command, that both logs to standard out and the log/development.log file.

Here is an example from the ActiveSupport::BroadcastLogger API:

stdout_logger = ActiveSupport::Logger.new(STDOUT)
file_logger = ActiveSupport::Logger.new("development.log")
broadcast = ActiveSupport::BroadcastLogger.new(stdout_logger, file_logger)

broadcast.i...

RSpec: Running examples by name (or running a single shared example)

When an Rspec example fails, I usually investigate by running that example again using rspec <file:line>. However, this does not work with shared examples, since Rspec doesn't know in which context the shared example should be run.

But there is a different way: You can run the shared example using the -e, --example option. It takes a string value and runs all scenarios containing that substring in their full description.

This allows you to run a single uniquely named example, all examples with
similar names, all the examples in a u...

A simple example with a GIN index in Rails for optimizing a ILIKE query

You can improve your LIKE / ILIKE search queries in PostgreSQL by adding a GIN index with an operate class ("opclass") to split the words into trigrams to the required columns.

Example

class AddSearchTextIndexToUsers < ActiveRecord::Migration[7.1]
  def change
    enable_extension 'pg_trgm'

    add_index :users, :search_tex...

Advanced plotting in Ruby with Gnuplot

Besides Plotting graphs in Ruby with Gruff, which comes handy for many uses cases, you sometimes might need configuration for more advanced plots, e.g. for academic concerns. Then using Gnuplot, the first academic open source plotting software, might be a good option.

There are several wrappers for Ruby available and I mainly looked at one of the two most frequently used ones, which are [ruby_gnuplot](https://github.com/rdp/ruby_gnuplot...

Defining custom RSpec matchers

There are three ways to define your own RSpec matchers, with increasing complexibility and options:

1) Use RSpec::Matchers.define

RSpec::Matchers.define :be_a_multiple_of do |expected|
  match do |actual|
    actual % expected == 0
  end
  
  # optional
  failure_message do |actual|
    "expected that #{actual} would be a multiple of #{expected}"
  end
  
  # optional
  failure_message_when_negated do |actual|
    "expected that #{actual} would not be a multiple of #{expected}"
  end
end
  • This is automatically available i...

Ruby: How to make your ruby library configurable

You might know a few examples, where you configure some library via a block. One example is the Rails configuration:

Rails.application.configure do |config|
  config.enable_reloading = false
end

This card describes a simple example on how to make your ruby library configurable.

Example

module FooClient
  class Client
    class_attribute :config

    def self.configure
      self.config ||= Configuration.new
      yield(config)
    end

    def test
      uri = URI.parse(FooClient::Client.config.endpoint)
      Net:...

Configuring ActionMailer host and protocol for URL generation

When you generate a URL in a mailer view, ActionMailer will raise an error unless you previously configured it which hostname to use.

There are two options to set the default_url_options of ActionMailer:

  1. Hardcoded solution (preferred solution when using Rails with ActiveJob/Sidekiq or Cronjobs)
  2. Dynamic solution

1. Hardcoded solution

When you are sending mails from outside the request cycle, e.g. ActiveJob/Sidekiq or Cronjobs, y...

Jasmine: Dealing with randomness

Whenever you have to deal with randomness in a jasmine test there are some spy strategies to help you out!

Let's say we have a method Random.shuffle(array) to shuffle an array randomly and a class that uses shuffle within the constructor.

returnValue & returnValues

it('shuffles the array', () => {
  spyOn(Random, 'shuffle').and.returnValue([3, 2, 1])
  array = [1, 2, 3]
  
  testedClass = new testedClass(array)
  
  expect(Random.shuffle).toHaveBeenCalled()
  expect(testedClass.array).toEqual([3, 2, 1])
})

If you have...

Enumerators in Ruby

Starting with Ruby 1.9, most #each methods can be called without a block, and will return an enumerator. This is what allows you to do things like

['foo', 'bar', 'baz'].each.with_index.collect { |name, index| name * index }
# -> ["", "bar", "bazbaz"]

If you write your own each method, it is useful to follow the same practice, i.e. write a method that

  • calls a given block for all entries
  • returns an enumerator, if no block is given

How to write a canonical each method

To write a m...

Preventing users from uploading malicious content

When you allow file uploads in your app, a user might upload content that hurts other users.

Our primary concern here is users uploading .html or .svg files that can run JavaScript and possibly hijack another user's session.

A secondary concern is that malicious users can upload executables (like an .exe or .scr file) and use your server to distribute it. However, modern operating systems usually warn before executing files that were downloaded from t...

Rails: Using custom config files with the config_for method

You can use the config.x configuration in combination with config_for to configure global settings for your Rails 4.2+ application.

Example

In your config/application.rb assign the settings from e.g. config/settings.yml as follows:

module FooApplication
  class Application < Rails::Application
    config.x.settings = config_for(:settings)
  end
end

The config/settings.yml might look as follows:

shared: &shared
  email: info@example.com
...

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

Verifying doubles in RSpec 3

RSpec 3 has verifying doubles. This breed of mock objects check that any methods being stubbed are present on an instance of a given class. They also check methods aren't called with the wrong number of arguments.

This dual approach allows you to move very quickly and test components in isolation, while
giving you confidence that your doubles are not a complete fiction.

You should always prefer using a verifying double to using an old-school mock...

Checklist for Implementing Design

We have a long-standing checklist for merge requests. However, it hardly matches the intricate requirements for design. This checklist fills the gap.

Before starting implementing, look at all designs: are there components similar to yours? Have they already been implemented? Can you build on this prior art when implementing yours?

Checklist: I confirm my design implementation

  • has been tested manually by me
  • adheres to the code style of the project (e.g. BEM)
  • avoids "magic numbers" (don't say e.g. ...

Open UI: Future development in web components and controls

tl;dr When browsers start to adapt proposals from Open UI, it might not be necessary to use any 3rd party libraries to have nice components and controls in web applications e.g. selects. It would require only a minimum of CSS and Javascript to get them working and looking good.

The purpose of the Open UI, a W3C Community Group, is to allow web developers to style and extend built-in web UI components and controls, such as <select> dropdowns, checkboxes, radio buttons, and date/color pickers.

To do that, we’ll need to fully speci...

Jasmine: Testing complex types for equality

Jasmine comes with two matchers that test for equality. The first is toBe:

expect(first).toBe(second)

toBe passes when first === second. Unfortunately this is useless for non-primitive values because JavaScript is a horrible language.

However, Jasmine comes with another matcher toEqual:

expect(first).toEqual(second)

This matcher behaves as a human would expect for types like the following:

  • Arrays
  • Objects
  • Nested array/object constructs
  • Regular expressions...

Heads up: Quering array columns only matches equally sorted arrays

Given you have an array column like this:

create_table "users", force: :cascade do |t|
  t.integer "movie_ids", default: [], array: true
end

You might think that the following queries yield the same result:

User.where(movie_ids: [16, 17])
User.where(movie_ids: [17, 16])

Turn's out - they are not! They do care about array ordering more than I do.

To query for identical arrays independent of their order you have to either:

  1. Sort both the query and database content. If you're on Rails 7.1 you can use the new [`normal...