Print capistrano execution time per server

Are all servers slow or just one ?

Output
Example logging all times > 3 seconds.

executing 'ruby -e "r = rand(10); puts %{sleep #{r}}; sleep r"'
 ** [out :: dbadmin1] sleep 6
 ** [out :: app1] sleep 4
 ** [out :: work1] sleep 2
 ** [out :: mirror1] sleep 5
 ** Server dbadmin1 finished command in 4 seconds
 ** Server app1 finished command in 7 seconds

Code

module PerServerTime
  # set via Capistrano::Command.per_server_time_threshold = 111
  def self.included(base)
    class << base
      attr_accessor :per_server_time_threshold
    end
  end

  # whenever a command finishes on 1 server print how long it ran on this server
  def process_iteration
    return super if @channels.size < 2 # do not need per-server logging for 1 server

    @start_time ||= Time.now.to_f
    @closed_channels ||= []

    @channels.each do |channel|
      if channel[:closed] && !@closed_channels.include?(channel)
        @closed_channels < (self.class.per_server_time_threshold || 30)
          host = channel.connection.instance_variable_get(:@xserver).host
          logger.info("Server #{host} finished command in #{time} seconds")
        end
      end
    end

    super
  end
end

Capistrano::Command.send(:include, PerServerTime)

Omniauth / OAuth integration test with webmock

Took me a while to figure out all the stubs … maybe this is useful later :)
I’m using this to test our facebook / twitter signup flows.

def external_redirect(url)
  yield
rescue ActionController::RoutingError # goes to twitter.com/oauth/authenticate
  current_url.must_equal url
else
  raise "Missing external redirect"
end

it "signs up" do
  twitter_id = 12345
  visit "/"
  
  stub_request(:post, "https://api.twitter.com/oauth/request_token").
    to_return(body: "oauth_token=TOKEN&oauth_token_secret=SECRET&oauth_callback_confirmed=true")

  external_redirect "https://api.twitter.com/oauth/authenticate?x_auth_access_type=read&oauth_token=TOKEN" do
    click_link "Continue with Twitter"
  end

  # https://dev.twitter.com/oauth/reference/post/oauth/access_token
  stub_request(:post, "https://api.twitter.com/oauth/access_token").
    to_return(body: "oauth_token=TOKEN&oauth_token_secret=SECRET&user_id=#{twitter_id}&screen_name=twitterapi")

  # https://dev.twitter.com/rest/reference/get/account/verify_credentials
  stub_request(:get, "https://api.twitter.com/1.1/account/verify_credentials.json?include_entities=false&skip_status=true").
    to_return(body: %{{"profile_image_url_https": "image_normal.png"}})

  visit "/auth/twitter/callback"
end

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 '"/' }