PSA: Umlauts are not always what they seem to be

When you have a string containing umlauts which don't behave as expected (are not matched with a regexp, can't be found with an SQL query, do not print correctly on LaTeX documents, etc), you may be encountering umlauts which are not actually umlaut characters.

They look, depending on the font, like their "real" umlaut counterpart:

  • ä ↔ ä
  • ö ↔ ö
  • ü ↔ ü

However, they are not the same:

'ä' == 'ä' # false
'ä'.size # 1
'ä'.size # 2

Looking at how those strings are constructed reveals what is going on:

'ä'.unpack('U*...

How to combine "change", "up", and "down" in a Rails migration

Rails migrations allow you to use a change method whose calls are automatically inverted for the down path. However, if you need to some path-specific logic (like SQL UPDATE statements) you can not define up and down methods at the same time.

If you were to define define all 3 of them, Rails would only run change and ignore up and down. However, Rails 4+ features a helper method called reversible:

class MyMigration < ActiveRecord::Migration

  def cha...

RubyMine users: you should be using bookmarks

RubyMine allows bookmarking lines of code. This is super-helpful when working on a complex problem.
I've been using this feature for a few years now, and so should you! :)

Here are the default Linux/Windows keystrokes. See the documentation for other keybindings.

Add an anonymous bookmark

F11

A gray checkmark will be shown in the gutter on the left.
If you press F11 again on a bookmarked line, the bookmark will be removed.

Add a named bookmark ("mnemonic")

Ctrl ...

How the Date Header Affects Cookie Expiration and Caching

tl;dr

When a cookie includes an Expires attribute or an HTTP response includes caching headers like Expires or Cache-Control, their validity depends on the server's Date header if present. Otherwise, the browser uses its local time. This can lead to issues in tests with mocked time or inconsistent cache behavior.

Cookie Expires depends on the Date header or browser time

When a cookie includes an Expires attribute, the browser evaluates the expiration date relative to a reference time:

  1. If the HTTP response ...

How to enable Rails' file_fixture helper in FactoryBot

In FactoryBot factories, Rails' file_fixture is not available by default. To enable it, include a support module from rspec-rails:

FactoryBot::SyntaxRunner.include(RSpec::Rails::FileFixtureSupport)

That includes ActiveSupport::Testing::FileFixtures, where file_fixture is defined, but also configures the file_fixture_path so that you can actually use file_fixture.

Description list support (Updated: List of Helpful RubyMine Shortcuts)

Team C enabled support for description lists in makandracards.
Please make sure to add a newline before the definition starting with colon (:).

Usage example:

Apple

: A fruit that grows on trees.

Orange

: A citrus fruit known for its vitamin C content.

  More details in second paragraph.

will result in:

Apple

A fruit that grows on trees.

Orange

A citrus fruit known for its vitamin C content.

More details in second paragraph.

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

How to tackle complex refactorings in big projects

Sometimes huge refactorings or refactoring of core concepts of your application are necessary for being able to meet new requirements or to keep your application maintainable on the long run. Here are some thoughts about how to approach such challenges.

Break it down

Try to break your refactoring down in different parts. Try to make tests green for each part of your refactoring as soon as possible and only move to the next big part if your tests are fixed. It's not a good idea to work for weeks or months and wait for all puzzle pieces ...

Getting permanent links to files on Github or Gitlab

Please don't simply copy line number links from Github. The URL usually contains a branch name like master which will change over time:

https://github.com/makandra/upjs/blob/master/lib/assets/javascripts/up/link.js.coffee#L76

If someone now posts an insertion or deletion to that file into master your link points to the wrong line!

A better way is to press the Y key after clicking on a line number. This will transform the URL to another URL that points to the particular commit:

https://github.com/makandra/upjs/blob/b3b14...

TestProf II: Factory therapy for your Ruby tests—Martian Chronicles, Evil Martians’ team blog

Some key highlights and points from the linked article TestProf II: Factory therapy for your Ruby tests.

The Problem with Factories in Ruby Tests

  • Factories are used to easily generate test data.
  • However, they can unintentionally slow down test suites by creating unnecessary or excessive associated data (factory cascades).

Understanding Factory-Induced Slowdowns

  • Factories often create additional data (e.g., associated records) th...

Fragment Caching in Rails 7.1+ requires Haml 6

Rails slightly changed the fragment cache implementation from Rails 7.0 to Rails 7.1. Unfortunately, this is incompatible with how Haml 5 patches Action View buffers. I tried turning a String buffer into an ActionView::OutputBuffer, but this brought up...

Text fragments in the browser URI fragment

Text fragments allow linking directly to a specific portion of text in a web document, without requiring the author to annotate it with an ID, using particular syntax in the URL fragment. Supporting browsers are free to choose how to draw attention to the linked text, e.g. with a color highlight and/or scrolling to the content on the page. This is useful because it allows web content authors to deep-link to other content they don't control, without relying on the presence of IDs to make that possible. Building on top of that, it could be u...

How to enable pretty IRB inspection for your Ruby class

When Ruby objects are inspected in any modern IRB, some objects (like ActiveRecord instances) are rendered with neat colors and line breaks.
You will not get that for custom classes by default -- which can be annoying if your inspection contains lots of meaningful information.

Here is what you need to do if you want your objects to be inspected nicely.

Implement a pretty_print method

As an example, consider the following class.

class MyClass

  # ...

  def inspect
    "#<#{self.class} attr1: #{attr1.inspect}, attr2: #{attr2...

Generating an Entity Relationship Diagram for your Rails application

This card explains how to generate an entity relationship diagram for your Rails application.
We also show how to limit your ERD to a subset of models, e.g. models inside a namespace.

Generating a full ERD

Option A: RubyMine

  1. Right-click anywhere in your project tree
  2. In the context menu, find the "Diagrams" menu item at/near the bottom
  3. Inside, choose "Show diagram" → "Rails Model Dependency Diagram"
  4. A new tab will open with the diagram inside. You can modify it there, and export it as an image.

Option B: Use rails-e...

Custom error messages in RSpec or Cucumber steps

Sometimes you have a test expectation but actually want a better error message in case of a failure. Here is how to do that.

Background

Consider this test:

expect(User.last).to be_present

In case of an error, it will fail with a not-so-helpful error message:

expected present? to return true, got false (Spec::Expectations::ExpectationNotMetError)

Solution

That can be fixed easily. RSpec expectations allow you to pass an error message like this:

expect(User.last).to be_present, 'Could not find a user!'

...

Heads up: counting may be slow in PostgreSQL

The linked article points out that COUNT queries might be unexpectedly slow in PostgreSQL.

If you just need to know "are there any records" use any?. This uses SELECT 1 AS one FROM ... LIMIT 1 under the hood.
If you just need to know "are there no records" use empty? or none?. This uses SELECT 1 AS one FROM ... LIMIT 1 under the hood.

In short: Replace foo.count > 0 with foo.any? or foo.count == 0 with foo.none?

If you require quick counts and can tolerate some level of imprecision, consider exploring the ...

Debugging Capistrano

Capistrano 3 has a doctor task that will print information about

  • Environment: Ruby, Rubygems and Bundler versions
  • List of Capistrano gems and whether an update is available
  • All config variables and their values
  • Capistrano server config
$ bundle exec cap staging doctor

Rails 7 adds #caching? and #uncacheable!

Rails' fragment caching caches subtrees of an HTML document tree. While constructing that tree though, it can be really hard to keep track of whether some code is run in a caching context. Fortunately, Rails 7 brings two helpers that simplify this.

Note that these helpers are all about Rails' fragment caching and not about downstream caching (i.e. Cache-Control).

uncacheable!

Invoke this helper in a partial or another helper that should never be cached. Used outside of fragment caches, the helper does just nothing. But should it ...

Enabling YJIT

YJIT is Ruby's default just-in-time compiler. It is considered production-ready since Ruby 3.2 (source).

To activate YJIT you need two steps:

  • Your ruby binary needs to be compiled with YJIT support.
  • You need to enable YJIT.

Getting a Ruby with YJIT support

We usually install Ruby with tools like rbenv or asdf. This compiles the ruby binary from the source code. Support for YJIT will be automatically added during this compilation...

Running Rubocop as a pre-push hook

Git has the concept of hooks: bash scripts that are invoked at certain points in the Git lifecycle. One handy use is a pre-push hook that runs Rubocop. It will prevent pushing code that Rubocop finds fault with.

Configuring the hook

Git hooks are normally stored locally with a repository. They are not committed.

  1. Store this snippet in .git/hooks/pre-push:
if [ -f ./.rubocop.yml ]; then                                                  
  echo 'Running Rubocop ...'                                                    
  bundle exe...