Improving Autocomplete Cache Hit Rates by Looking up Parent Caches

When a user asks for all the words starting with “foobar” let’s check the cache for fooba,foob,foo,fo,f and see if any of these results already include a full set of words.
This saved us ~30% of our db queries and makes the ui more responsive.

# given a start word of foobar check caches for ["fooba", "foob", "foo", "fo", "f"]
# to see if any of the simpler queries already had a complete response
def cached_autocomplete(start)
  limit = 1000
  short = start.dup
  shorter = Array.new(start.size - 1){ short.chop!.dup }

  cached = Rails.cache.read_multi(*shorter.map { |s| "autocomplete-#{s}" })
  cached.each_value do |tags|
    if tags.size < limit # complete response / nothing is missing
      tags.select! { |t| t.start_with?(start) } # filter non-matches
      return tags
    end
  end

  Rails.cache.fetch("autocomplete-#{start}", expires_in: 15.minutes, race_condition_ttl: 1.minute) do
    ... hit the db ... limit(limit) ...
    ... prevent multiple cache refreshes with race_condition_ttl ...
  end
end

ActionMailer / Rails: No paths in my mails please

Always paying attention that mails only use urls is a bit annoying/dangerous and also means we cannot reuse partials and cannot use nice resource routes like `link_to user.name, user`

Make ActionMailer always use full urls:

# we only want urls in our emails, never paths
module OnlyAbsoluteUrls
  def url_for(*args)
    url = super
    if url.include?("://")
      url
    else
      "#{ActionMailer::Base.default_url_options.fetch(:host)}#{url}"
    end
  end
end

class ApplicationMailer < ActionMailer::Base
  helper OnlyAbsoluteUrls
end

and to make sure it works let’s verify in all mailer tests that we did not actually generated paths:

after { deliveries.map(&:body).map(&:to_s).join.wont_include '"/' }

Running karma js with rails asset pipeline / sprockets

# test/karma.conf.js
...
    basePath: '<%= Bundler.root %>',
...
    files: [
      '<%= resolve_asset('vis.js') %>',
      'app/assets/javascripts/app.js',
      'test/**/*_spec.js'
    ],

# Rakefile
namespace :test do
  task js: :environment do
    with_tmp_karma_config do |config|
      sh "./node_modules/karma/bin/karma start #{config} --single-run"
    end
  end

  private

  def with_tmp_karma_config
    Tempfile.open('karma.js') do |f|
      f.write ERB.new(File.read('test/karma.conf.js')).result(binding)
      f.flush
      yield f.path
    end
  end

  def resolve_asset(file)
    Rails.application.assets.find_asset(file).to_a.first.pathname.to_s
  end
end

Development checks for fast boottime

A few simple checks to make sure we are not regressing into slow boot times by preloading to many things. Needs to be the last initializer so it catches what all the others are doing -> zz.rb

Code

# config/initializers/zz.rb
if Rails.env.development?
  # make sure we do not regress into slow startup time by preloading to much
  Rails.configuration.after_initialize do
    [
      ActiveRecord::Base.send(:descendants).map(&:name),
      ActionController::Base.descendants.map(&:name),
      (File.basename($0) != "rake" && defined?(Rake) && "rake"),
    ].compact.flatten.each { |c| raise "#{c} should not be loaded" }
  end
end

Result

config/initializers/zz.rb:9:in `block (2 levels) in ': 
User should not be loaded (RuntimeError)