Enqueue into Sidekiq via pure Redis (without loading sidekiq)

We want to enqueue jobs, but do not want to blow up the app with sidekiq and it’s dependencies.

Usage

RawSidekiq.enqueue("XyzJob", [1,2,3], :namespace => "custom")

Code

# https://grosser.it/2013/01/17/enqueue-into-sidekiq-via-pure-redis-without-loading-sidekiq
require "json"
require "redis"
require "securerandom"

class RawSidekiq
  def self.enqueue(queue, klass, args, options={})
    payload = {
      'class' => klass,
      'args' => args,
      'jid' => SecureRandom.hex(12),
      #'retry' => true
    }.to_json

    conn = Redis.new
    conn.multi do
      conn.sadd([options[:namespace], "queues"].compact.join(":"), queue)
      conn.lpush([options[:namespace], "queue", queue].compact.join(":"), payload)
    end
  end
end

Kill ActiveRecord observers

Killing observers

  • decreases startup time by not loading all your models
  • makes it possible to preload config/environment.rb and still test models -> spin/zeus
  • makes dependencies obvious
  • replaces ActiveRecord magic with ruby
  • makes your app Rails 4 ready

Before:

# config/environment.rb
config.observers = [:foo_observer] 

# app/observers/foo_observer.rb
class FooObserver < ActiveRecord::Observer
  observes :user

  def after_save(user)
    ....
  end
end

After:

# app/models/user.rb
class User < ActiveRecord::Base
  include FooObserver
end

# app/observers/foo_observer.rb
module FooObserver
  class << self
    def included(base)
      this = self
      base.after_save{|user| this.more_descriptive_name(user) }
    end

    def more_descriptive_name(user)
      ...
    end
  end
end

Upgrading to rails 3.0 — making sure you use rack headers everywhere

Normal headers like Accept or :authorization do not work in rails 3 integration tests and you need to convert everything to HTTP_ACCEPT etc, to help find all those places and make sure you do not introduce new bugs in rails 2 add this:

# https://grosser.it/2012/10/19/upgrading-to-rails-3-0-making-sure-you-use-rack-headers-everywhere/
# message can be changed on rails 3, but keep the warning, it's so hard to track down missing headers
# maybe try to remove in rails 3.1+
# can be tested by e.g. changing header to Accept instead of HTTP_ACCEPT
class ActionController::Integration::Session
  # headers that are only used by our code and not rails/rack can be whitelisted, but make sure they work on rails 2 and 3
  HEADER_WHITELIST = ['Funky-Headers-You-Have-To-Use']
  def process_with_header_warning(*args)
    if args[3] && bad = args[3].keys.detect{|k| !k.is_a?(String) || (!HEADER_WHITELIST.include?(k) && k !~ /^[A-Z_\d]+$/) }
      raise "Header #{bad} will not work on rails 3, please uppercase (Content-Type -> CONTENT_TYPE) and prefix HTTP_ (Accept -> HTTP_ACCEPT)"
    end
    process_without_header_warning(*args)
  end
  alias_method_chain :process, :header_warning
end

Rails 2: Your integration tests are lying

Integration tests call the whole rack middleware stack, but stubbornly return the last controller response, which can be completely different especially if you use warden or other middleware-tools.

See actionpack-2.3.14/lib/action_controller/integration.rb:342

To fix that and prepare for Rails 3 (which also relies on rack response) do this:

# test/test_helper.rb

if Rails::VERSION::MAJOR == 2
  # https://grosser.it/2012/10/19/rails-2-your-integration-tests-are-lying/
  # make integration tests use rack response, so we can test our middlewares
  # and not only the pure controller response

  ActionController::Base.class_eval do
    # this is usually done just-in-time by #process but we need to do it earlier
    include ActionController::Integration::ControllerCapture

    # then we hide last_instantiation from #process
    def self.last_instantiation;end
  end

  ActionController::Integration::Session.class_eval do
    def process_with_rackify(*args)
      process_without_rackify(*args)
    ensure
      # needed e.g. inside of assert_redirect_to
      capture = ActionController::Integration::ControllerCapture::ClassMethods

      # not set by original #process
      @response.redirected_to = @response.headers["Location"] if @response
      if @controller = capture.send(:class_variable_get, :@@last_instantiation)
        @request = @controller.request
        @response.template = @controller.response.template if @controller.response
        @controller.send(:set_test_assigns)
      end
    end
    alias_method_chain :process, :rackify
  end
end

Making sure you are not creating unwanted actions by including Modules

This will blow up if someone includes a module with public methods that would count as actions.

# application_controller_test.rb
class ApplicationControllerTest < ActionController::TestCase
  class CleanController < ApplicationController
  end

  test "should be clean" do
    assert_equal [], CleanController.action_methods.to_a.
      reject{|a| a =~ /^_conditional_callback_around_/}
  end
end