Learn how to use ruby/debug

This talk shows simple and advanced usages of a debugger. It goes through a step by step debugging workflow.

Here are some command examples:

(rdbg) step 2 # step twice
(rdbg) info # show current scope, including self
(rdbg) bt # show backtrace
(rdbg) frame 3 # go directly to frame 3
(rdbg) break User#email # add a breakpoint in the email instance method
(rdbg) catch SomeException # break when SomeException is raised

Some advanced examples for scriptable breakpoints:

debugger(pre:...

Open Terminator from nautilus context menu

On our Ubuntu machines we have nautilus file manager with nautilus-extension-gnome-terminal installed. This adds an entry to the context menu (right click) to start a gnome-terminal in the current directory. As I'm mostly using Terminator terminal, I wanted to have a similar context menu entry to launch Terminator directly. I came across this python script that does exactly that.

  • Install python3-nautilus: sudo apt install python3-nautilus
  • Create `/usr/share/nautilus-...

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

High-level data types with "composed_of"

I recently stumbled upon the Rails feature composed_of. One of our applications dealt with a lot of addresses and they were implemented as 7 separate columns in the DB and Rails models. This seemed like a perfect use case to try out this feature.

TLDR

The feature is still a VERY leaky abstraction. I ran into a lot of ugly edge cases.

It also doesn't solve the question of UI. We like to use simple_form. It's currently not possible to simply write `f...

How to allow testing beforeunload confirmation dialogs with modern ChromeDrivers

Starting with ChromeDriver 127, if your application displays a beforeunload confirmation dialog, ChromeDriver will immediately close it. In consequence, any automated tests which try to interact with unload prompts will fail.

This is because ChromeDriver now follows the W3C WebDriver spec which states that any unload prompts should be closed automatically.
However, this applies only to "HTTP" test sessions, i.e. what you're using by default. The spec also defines that bi-directional test se...

Updated: Using tig

Re-organized card.

Config file is highly recommended!

Optimizing images for the web

For webpages to load fast it's recommended to optimize images. Ideally an image's file size should be as small as possible while still being of decent quality. This card demonstrates two command line tools for image optimization

Use identify to fetch information about pictures. convert can change size/quality and strip meta information. Both commands are supplied by ImageMagick.

$ identify in.jpg
in.jpg JPEG 294x440 294x440+0+0 8-bit sRGB 92.8KB 0.000u 0:00.000
$ convert in.jpg -resize x220 -strip -set profile sRGB2014.icc -qua...

Don't require files in random order

A common pattern in Ruby is to to require all files in a specific diretory, using something like

Dir.glob(Rails.root.join('lib/ext/**/*.rb')).each do |filename|
  require filename
end

However, this causes files to be required in an order determined by the file system. Since load order can be important, this may lead to different behavior on different machines which are hard to debug.

Simply add a .sort:

Dir.glob(Rails.root.join('lib/ext/**/*.rb')).sort.each do |filename|
  require filename
end

Ruby 3

...

Git: How to configure git to push only your current branch

You can change which branches will be pushed when saying git push. Our recommendation is to set it to current.

From the git-config documentation:

push.default
Defines the action git push should take if no refspec is given on the command line, no refspec is configured in the remote, and no refspec is implied by any of the options given on the command line. Possible values are:

  • nothing - do not push anything.
  • `matchin...

Rails: How to stub the env in Rails 7+

Rails 7.1 added a new method Rails.env.local?. If you want to stub the Rails env correctly, use ActiveSupport::EnvironmentInquirer like this:

# check if the value of stubbed_env is valid
allow(Rails).to receive(:env).and_return(ActiveSupport::EnvironmentInquirer.new(stubbed_env.to_s))

esbuild: Compressing JavaScript harder with Terser

esbuild comes with a minifier that is good enough for most cases. If you're looking to squeeze out as many bytes as possible, you can consider compressing with Terser instead.

Using Terser will increase your build times significantly, but produce the smallest output:

| | Terser (3 pass) | Terser (1 pass) | esbuild |
|----------------------------|-----------------------|------------------|-------...

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

Logging multiple lines in Rails without making filtering your logs difficult

Rails' default logger prefixes each log entry with timestamp and tags (like request ID).
For multi-line entries, only the first line is prefixed which can give you a hard time when grepping logs.

Example

Rails.logger.info(<<~TEXT)
  Response from example.com:
  Status: 200
  Body: It works!
TEXT

With that, the following is written to your log file.

I, [2024-10-04T08:12:16.576463 #1917250]  INFO -- : [97e45eae-a220-412d-96ad-e9e148ead71d] Response from example.com:
Status: 200
Body: It works!

If you then run `grep...

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

Your Rails sandbox console

Just found out about a great feature in Rails that seems to be around since Rails 2. Start a console with the --sandbox (or -s) parameter:

rails console --sandbox

All changes you make to the database will be rolled back on exit.

Warning

Changes beyond the database (deleting files, sending emails, etc) cannot be rolled back!

Understanding race conditions with duplicate unique keys in Rails

validates_uniqueness_of is not sufficient to ensure the uniqueness of a value. The reason for this is that in production, multiple worker processes can cause race conditions:

  1. Two concurrent requests try to create a user with the same name (and we want user names to be unique)
  2. The requests are accepted on the server by two worker processes who will now process them in parallel
  3. Both requests scan the users table and see that the name is available
  4. Both requests pass validation and create a user with the seemingly available name...

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

Terser is good at minifying JavaScript

Terser is a really good minifier ("compressor") for JavaScript code. I'm often surprised by the thoughtfulness of its compressed output.

Let's take this function:

function fn() {
  if (a) {
    return 'foo'
  } else if (b) {
    return 'foo'
  } else {
    return c()
  }
}

console.log(fn())

Terser will reduce this to the following code:

console.log(a||b?"foo":c())

Note how:

  • The if statement has been replaced by a tertiary expression. This is often less readable, but it doesn't matter in c...

HTML/CSS: "transparent" is not a color

Heads up: transparent is not a real color, but black with 0% opacity.

In transparent gradients, this adds some gray shades:

/* ❌ Looks dull in older browsers */
linear-gradient(to right, white 0%, transparent 100%)

Browser vendors have soon found a fix, but Safari only implemented it in 15.4. Should you need to support older versions, here is how:

/* ✅ Fix for older browsers: transparent white */
linear-gradient(to right, white 0%, rgba(255,255,255,0) 100%)

PostgreSQL and its way of sorting strings

PostgreSQL uses the C library's locale facilities for sorting strings:

  • First, all the letters are compared, ignoring spaces and punctuation.
  • It sorts upper and lower case letters together. So the order will be something like a A b B c C
  • Then, spaces and punctuation are compared to break ties.

Example:

Ruby PostgreSQL
IMAGE3.jpg image2.jpg
image.jpg image3.jpg
image2.jpg IMAGE3.jpg
image3.jpg image.jpg

Further reading

  • [PostgreSQL-FAQ: Why do my strings sort incorrectly?](h...

Run all RSpec tests edited or added in the current branch

With this command you can run all the spec files which have been edited or added in the current branch since master:

git diff --name-only master -- ./spec | xargs -I{} rspec {} 
  • If you have several spec folders add them for path parameter after ./spec accordingly.
  • The option -I{} creates a placeholder to be replaced.
  • You can also compare edited/added specs between commits with <commit>..<commit>

Jasmine: Use `throwUnless` for testing-library's `waitFor`

testing-library are widely used testing utilities libraries for javascript dependent frontend testing. The main utilities provided are query methods, user interactions, dom expectations and interacting with components of several frontend frameworks, which allows us to worry less about the details happening in the browser and focus more on user centric tests instead!


Some of the time you will find a necessity to use methods like [waitFor](https://testing-library.com/docs/dom-testing-library/api-async/...

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