Gitlab: How to cancel redundant pipelines

In the Gitlab settings the flag Auto-cancel redundant pipelines is enabled by default. This auto-cancels pipelines with jobs that have the interruptible setting set to true (defaults to false e.g. to not cancel deploys by accident).

Consider setting the interruptible flag for test jobs to reduce the load on your runners like in the following example .gitlab-ci.yml:

rubocop:
  interruptible: true
  script:
    - 'bundle exec ruboco...

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

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

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

Do not forget mailer previews

When changing code in mailers, updating the corresponding mailer preview can be forgotten very easily.

Mailer previews can be tested like other code as well and I sometimes add the following tests to test suites:

# Make sure to require the previews
Dir[Rails.root.join('spec/mailers/previews/*.rb')].each { |file| require(file) }


ActionMailer::Preview.all.index_with(&:emails).each do |preview, mails|
mails.each do |mail|
    describe preview do
      specify "##{mail} works" do
        expect { preview.call(mail...

RSpec: Scoping custom matchers to example groups

When you find yourself in the situation that you would like to define a custom matcher in your specs, but you do not want to define a global matcher since you need it only for your specific test, there are two ways to do it:

Custom matcher for a single group

If you're only going to include a matcher once, you can also use the matcher macro within an example group:

describe "group" do
  
  matcher :be_just_like do |expected|
    match {|ac...

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

RSpec 3 allows chaining multiple expectations

When you are using lambdas in RSpec to assert certain changes of a call, you know this syntax:

expect { playlist.destroy }.to change { Playlist.count }.by(-1)

While you can define multiple assertions through multiple specs, you may not want to do so, e.g. for performance or for the sake of mental overhead.

Multiple expectations on the same subject

RSpec allows chaining expectations simply by using and.

expect { playlist.destroy }
  .to change { Playlist.count }.by(-1)
  .and not_change { Video.count }

...

JavaScript: Sharing content with the native share dialog

Mobile Chrome and Safari support the "web share API" which allow you to use the native share functionality of an Android or iOS phone. Some desktop OSs like Windows or MacOS also support native share dialogs. See Can I Use for a detailed support matrix.

When clicking a share button using this API, the browser will automatically show all installed applications that support content sharing, such as Whatsapp, Facebook, Twitter, e-mail etc.

The API is extremely simple to use:

if ...

RSpec: Run a single spec (Example or ExampleGroup)

RSpec allows you to mark a single Example/ExampleGroup so that only this will be run. This is very useful when using a test runner like guard.

Add the following config to spec/spec_helper.rb:

RSpec.configure do |config|
  # These two settings work together to allow you to limit a spec run
  # to individual examples or groups you care about by tagging them with
  # `:focus` metadata. When nothing is tagged with `:focus`, all examples
  # get run.
  config.filter_run_including :focus => true
  config.run_all_when_everything_filtere...

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: Preloading associations in loaded records

Sometimes you want to fetch associations for an ActiveRecord that you already loaded, e.g. when it has deeply nested associations.

Edge Rider gives your models a static method preload_associations. The method can be used to preload associations for loaded objects like this:

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    @user.preload_associations(threads: { posts: :author }, messages: :sender)
  end
end

The attached initializers re...

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

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

How to push to Git without running CI on GitLab CI, GitHub Actions, or Travis CI

If a project ist configured to spawn CI runners for tests or deployment when pushing to the Repo, a habit of pushing WIP commits regularly may conflict with that.
Here are two solutions that allow you to keep pushing whenever you feel like it.

Special commit message

To skip a CI run, simply add [ci skip] or [skip ci] to your commit message. Example:

git commit -m "wip authentication [ci skip]"

Git push options (GitLab)

In addition to that, GitLab CI supports Git push options. Instead of changing your commit message, ...

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

Devise: Don't forget to lock users with soft delete

There are two ways to lock a user in devise.

  1. Using the lockable module
  2. Customizing the user account status validation when logging in.

It depends on your requirements which methods works best.

Locking a user on soft delete

We recommend to use option 2 when you want to couple the lock to the m...

Heads Up: Selenium 4 uses a binary to determine the chromedriver

I recently stumbled over a problem that my feature tests broke in CI because of a mismatching chromedriver version.

In this specific project we have a fixed Chromium version in a Debian 12 environment instead of Chrome. The tests however used a recent chrome version instead.

$ chromedriver --version
ChromeDriver 117.0.5938.149 (e3344ddefa12e60436fa28c81cf207c1afb4d0a9-refs/branch-heads/5938@{#1539})
$ chromium --version
Chromium 117.0.5938.149 built on Debian 12.1, running on Debian 12.1

> WARN Selenium [:selenium_manager] The chromed...

How to write modular code

Or: How to avoid and refactor spaghetti code

Please note that I tried to keep the examples small. The effects of the methods in this card are of course much more significant with real / more complex code.

What are the benefits of more modular code?

Code is written once but read often (by your future self and other developers who have to understand it in order to make changes for example). With more modular code you reduce the scope of what has to be understood in order to change something. Also, naming things gives you the opportunity t...

Use <input type="number"> for numeric form fields

Any form fields where users enter numbers should be an <input type="number">.

Numeric inputs have several benefits over <input type="text">:

  • On mobile or tablet devices, number fields show a special virtual keyboard that shows mostly digit buttons.
  • Decimal values will be formatted using the user's language settings.
    For example, German users will see 1,23 for <input type="number" value="1.23">.
  • Values in the JavaScript API or when submitting forms to the server will always use a point as decimal separator (i.e. "1.23" eve...

Bash: How to count and sort requests by IP from the access logs

Example

87.140.79.42 - - [23/Jan/2024:09:00:46 +0100] "GET /monitoring/pings/ HTTP/1.1" 200 814 "-" "Ruby"
87.140.79.42 - - [23/Jan/2024:09:00:46 +0100] "GET /monitoring/pings/ HTTP/1.1" 200 814 "-" "Ruby"
87.140.79.41 - - [23/Jan/2024:09:00:46 +0100] "GET /monitoring/pings/ HTTP/1.1" 200 814 "-" "Ruby"
87.140.79.42 - - [23/Jan/2024:09:00:46 +0100] "GET /monitoring/pings/ HTTP/1.1" 200 814 "-" "Ruby"

Goal

Count and sort the number of requests for a single IP address.

Bash Command

awk '{ print $1}' test.log | sort...

Ruby: How to use global variables for a conditional debugger

You can share a state in Ruby with global variables. Even if you should avoid them whenever possible, for debugging an application this could be temporary quite handy.

Example:

class User

  after_save { byebug if $debug; nil }

  def lock
   self.locked = true
   save
  end

end
Rspec.describe User do

  let(:user) { create(:user) } 

  before do
   # Many users are created and saved in this hook, but we don't want the debugger to stop for them...

Why two Ruby Time objects are not equal, although they appear to be

So you are comparing two Time objects in an RSpec example, and they are not equal, although they look equal:

expected: Tue May 01 21:59:59 UTC 2007,
     got: Tue May 01 21:59:59 UTC 2007 (using ==)

The reason for this is that Time actually tracks fractions of a second, although #to_s doesn't say so and even though you probably only care about seconds. This means that two consecutive calls of Time.now probably return two inequal values.

Consider freezing time in your tests so it is not dependent on the speed of the executi...