Sep 18, 2025 rails · testing · performance · fixtures · factorybot

This One Weird Trick Cut Our Rails Test Suite Time by 95% (FactoryBot Fans HATE It!)

Ever stared at your test suite taking forever and thought “there’s gotta be a better way”? Yeah, me too.

We had API specs taking 1 minute and 40 seconds to run. The culprit? Creating 121 interconnected taxons through FactoryBot for every single test. Each one triggering expensive nested set calculations, permalink generation, and enough ActiveRecord callbacks to make your CPU weep.

What’s Killing Your Test Suite

Most Rails apps treat all test data the same - everything goes through FactoryBot. But here’s the thing: not all test data is created equal.

You’ve got two types: - Static reference data - taxonomies, categories, sizes, colors that rarely change - Dynamic test data - users, orders, t-shirts that vary per test

We were recreating the same 121 taxonomy records over and over. That’s just wasteful.

Here’s How We Fixed It

Use Rails fixtures for the expensive static stuff, keep FactoryBot for everything else.

Look for data that’s expensive to create and needed everywhere:

# This was killing us - 121 taxons with complex nesting
def load_categories_taxonomy
  with_taxonomy("Category", relative_permalink: true) do |b|
    b.create_taxon("Men's T-Shirts")
    b.create_taxon("Women's T-Shirts")
    # ... 100+ more with expensive callbacks
  end
end

Create YAML files instead:

# spec/fixtures/spree_taxons.yml
category_root:
  name: "Category"
  permalink: "category"
  taxonomy: category

mens_tshirts:
  name: "Men's T-Shirts"
  permalink: "category/mens-tshirts"
  taxonomy: category
  parent: category_root

Replace expensive object creation:

# Before: Individual creation with callbacks
def load_taxonomies
  load_size_taxonomy
  load_categories_taxonomy
  # ... 8 more expensive methods
  Spree::Taxon.rebuild! # Ouch
end

# After: Bulk fixture loading
def load_taxonomies_from_fixtures
  ActiveRecord::FixtureSet.create_fixtures(
    Rails.root.join("spec/fixtures"),
    [:spree_taxonomies, :spree_taxons]
  )
end

The Secret Sauce Behind Fixtures

Rails fixtures skip the expensive stuff: - No ActiveRecord callbacks - Bulk SQL inserts instead of individual ones - Clean reference syntax with automatic foreign key resolution

The Numbers Don’t Lie

From 1m 40s to 7s. That’s 95% faster.

Your Decision Matrix

Use fixtures for: Reference data, complex hierarchical data, anything with expensive callbacks

Keep FactoryBot for: Test-specific data, data that varies per test, small datasets

Making It Work in Practice

Don’t convert everything at once. Profile your slowest tests, extract static data first, keep both approaches during transition.

Sometimes you need both:

FactoryBot.define do
  factory :product do
    name { "Test T-Shirt" }
    # Reference fixture data for expensive stuff
    category { Spree::Taxon.find_by(permalink: "category/mens-tshirts") }
    # Use factories for test-specific data
    designer { create(:designer) }
  end
end

The Bottom Line

The 80/20 rule applies here. 80% of your test performance problems come from 20% of your data creation.

Find that 20% and fix it with fixtures. Keep factories for everything else.

Profile first, be strategic, measure results. Fast tests = faster development cycles.

What’s your biggest test performance bottleneck?

Sep 5, 2025 macos · swift · appearance · dark-mode

A Swift oneliner to detect Dark mode

The old defaults read -g AppleInterfaceStyle trick is unreliable on Sequoia. Sometimes the key is missing, sometimes it only shows Dark. I switched to Swift and asked the system directly.

Drop this into ~/bin/appearance.swift:

#!/usr/bin/env swiftc -o /Users/elia/bin/appearance

import AppKit

print(
  NSApplication.shared.effectiveAppearance.name.rawValue.contains("Dark") ? "Dark" : "Light"
)

Make it executable and build it once:

chmod +x ~/bin/appearance.swift
~/bin/appearance.swift

That compiles a tiny binary at ~/bin/appearance. From then on:

appearance # => Dark

prints Dark or Light instantly. Edit the .swift and rerun it any time to rebuild in place. Clean, fast, and it works on Sequoia.

Jul 15, 2025 rails · console · development · ruby

Custom Colorized Environment String in Rails Console

Ever opened a Rails console and wondered “wait, which environment am I in again?” Yeah, me too. Especially when you’re switching between development, staging, and production faster than you can say User.destroy_all.

Here’s a neat trick to add some color to your console prompt so you never have to wonder again.

The Problem

The default Rails console visual feedback stops being useful because staging environments are usually configured as “production” on Heroku and most deployment platforms. So Rails.env.production? returns true for both staging and production. Fun times!

The Solution

Drop this into config/initializers/console.rb:

# frozen_string_literal: true

require "rails/commands/console/irb_console"

module IRBConsoleWithDeployEnv
  def initialize(app, env)
    super(app)
    @deploy_env = env
  end

  def colorized_env
    IRB::Color.const_set(:DIM, 2) unless defined?(IRB::Color::DIM)

    return super unless @deploy_env

    IRB::Color.colorize("live:", [:DIM]) +
      case @deploy_env
      when "staging"
        IRB::Color.colorize("stag", [:YELLOW])
      when "production"
        IRB::Color.colorize("prod", [:RED])
      else
        IRB::Color.colorize(@deploy_env, [:BLUE])
      end
  end

  Rails::Console::IRBConsole.prepend self
end

And this into config/application.rb:

module MyApp
  class Application < Rails::Application
    # Other configurations...

    console do |app|
      config.console = Rails::Console::IRBConsole.new(app, ENV["DEPLOY_ENV"])
    end
  end
end

Now your console prompt will show a nice colorized environment string prefixed by your app name. No more “oops, wrong environment” moments.

Feb 16, 2022 git · development · pairing

Add Co-Authored-By to all commits after pairing

TL;DR

Here’s the full solution:

git filter-branch --msg-filter "ruby -e'puts [$stdin.read,nil,*ARGV].join(%{\\n})' -- 'Co-Authored-By: John Doe <[email protected]>'" origin/master..HEAD

Now let’s break it down, piece by piece.

The commits

This will list all commits that are in your branch but not on origin/master

origin/master..HEAD

The filter

The simplest ruby script that will:

  1. read the original commit message from $stdin
  2. append any provided argument with a new line
puts [
  $stdin.read,
  nil, # extra empty line, for good measure
  *ARGV
].join(%{\n})

And we use -- to separate file names to execute from CLI arguments:

$ ruby -e 'p ARGV' -- "anything after '--'" "ends up in ARGV"
["anything after '--'", "ends up in ARGV"]

Putting it all together

We’re lucky git filter-branch has an option for changing commit messages, we’ll use that to append Co-Authored-By: … information to every commit in the current branch.

The --msg-filter option will execute a script that will take the old message as its standard input and use the standard output as the new message.

git filter-branch --msg-filter "ruby -e'puts [$stdin.read,nil,*ARGV].join(%{\\n})' -- 'Co-Authored-By: John Doe <[email protected]>'" origin/master..HEAD

The git-rebase option no-one knows that will shock your coworkers!

A few weeks ago, a moment before pushing upstream a glorious bug-fix, I ran the test suite locally, just to discover that I broke it!

Having lost all confidence in the commit history, I was out for blood, looking for the piece of code that ruined the whole PR!

With more than a hundred git aliases I consider myself quite a git nerd, so I reached out for a tool I knew: git rebase.

My plan was:

  1. mark each commit with edit
  2. run the test suite with bin/rake at each stop
  3. identify the offending commit and fix whatever bugs I encountered
  4. amend the commit and call it a day

That didn’t feel right tho, it was too clunky, I had a hunch that I could have done better than this! So, after reading once again the git rebase documentation, this little option popped out: git rebase --exec!

What --exec does to git rebase

The magic of --exec is that it will execute a command after each commit in your interactive rebase. If the command fails, it will stop the rebase, allow you to fix whatever needs to be fixed, and move along with git rebase --continue.

This means that I was able to swiftly run this command after each commit and ensure that I had a green suite at every step of the PR.

git rebase --interactive --exec "bin/rake" origin/master

Don’t forget that this neat trick can be used for all kinds of problems, like, for example, running a linter for every commit of a PR.

Nov 4, 2013 opal · ruby · euruko · lightning · talk · 2013

Euruko 2013 Opal lightining talk

For those who missed it here's the lightning talk I gave in Athens at Euruko 2013 (at minute 17:00):

euruko-talk-poster
Downlaod it here

(beware: the slides in the video have the wrong aspect raito)

Oct 10, 2013 opal · ruby · browser · meh · tabs · spaces

Reading meh's code

jonathan-goldsmith-eyes

I don't always read meh's code
but when I do it, I convert tabs to spaces.

The bookmarklet: ↦ meh` (drag it to your bookmark bar)

UPDATE: Now works with GitHub expanding tabs to non-breaking-spaces (history here)

Aug 21, 2013 opal · ruby · browser · talk · slides · video · rails · javascript · italian

Opal Talk at RubyDay 2013 video availble!

Hey, the talk I gave at this year's (2013) RubyDay is now on youtube!
I'll update the post with the slides soon :)

(LANGUAGE: Italian).

Jul 3, 2013 ruby · rails · rails3 · railsapi · sdoc · documentation · docs · ruby2 · github · api

Past and Future of Ruby and Rails

As a sad orphan of railsapi.com I’m proud to present you valuable links to ruby and rails documentation in SDoc format:

Searchable Ruby 2.0 API documentation

http://elia.github.io/railsapi.com/public/doc/ruby-v2.0/

Searchable Rails 3.2 API documentation

http://elia.github.io/railsapi.com/public/doc/rails-v3.2/

Mar 4, 2013 git · autocomplete · shorthand · shortcuts · bash

Autocomplete "git" as "g"

For all you lazy Git bums, here’s how you can type “g” got “git” and still have autocompletion (paste into your ~/.bash_profile or .dotfiles system):

alias g='git'
complete -o bashdefault -o default -o nospace -F _git g 2>/dev/null \
    || complete -o default -o nospace -F _git g