Rigel Group

They shoot Yaks, don't they?

Torquebox Shims for MRI Ruby

Torquebox is hands down the best way to deploy a high-performance, highly available Rails application. All the power of Java/JVM/JBOSS, with none of the XML. :) But, doing your development on Torquebox is still a bit of a pain when compared to rails s or using POW. With a little effort, you can build a Rails app that will run fine on MRI as well as on JRuby and under Torquebox.

For certain things, Rails makes it easy to choose different subsystems depending on the environment you are running in. So, if you want to take advantage of Infinispan caching when running under Torquebox in production, but use the standard in-memory cache in development/MRI, then in your production.rb you say config.cache_store = :torque_box_store, and in development.rb you say config.cache_store = :memory_store.

For other things, we can set up shims for Torquebox-specific features when running under MRI. The first thing we need to do is determine when we are running in a Torquebox environment, and when we are not. The simplest thing is usually the best, so lets set a system level environment variable called INSIDE_TORQUEBOX to true, and use that to activate the shims. In production we can do this in our startup scripts just before we launch Torquebox.

Now, back in our Rails app, we have an initializer called 00_torquebox.rb that looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
if ENV["INSIDE_TORQUEBOX"] == "true"
  Rails.logger.info "[TorqueBoxInitializer] Inside TorqueBox"
else
  Rails.logger.info "[TorqueBoxInitializer] NOT inside Torquebox, shimming TB functions..."
  module TorqueBox
    module Messaging
      class MessageProcessor
        attr_accessor :message

        def initialize
          @message = nil
        end

        def on_error(error)
          raise error
        end

        def on_message(body)
          throw "Your subclass must implement on_message(body)"
        end

        def process!(message)
          @message = message
          begin
            on_message( message.decode )
          rescue Exception => e
            on_error( e )
          end
        end
      end #MessageProcessor

      module Backgroundable
        extend ActiveSupport::Concern
        module ClassMethods
          def always_background(*methods)
          end
        end
      end #Backgroundable

    end #Messaging

    module Injectors
      # Look in the torquebox.yml file to map a queue name to a class
      def self.config
        @@config ||= YAML.load_file("#{Rails.root}/config/torquebox.yml")
      end

      class FakeQueue
        def initialize(klass)
          @klass = klass
        end
        def publish(msg)
          Rails.logger.info "[Queue] USING FAKE QUEUE #{@klass.name} -- PROCESSING EVENT DIRECTLY!"
          k = @klass.new
          k.on_message(msg)
        end
      end

      def fetch(queue_name)
        klass = Injectors.config['messaging'][queue_name].constantize
        FakeQueue.new(klass)
      end
      alias_method :inject, :fetch
      alias_method :__inject__, :fetch
    end #Injectors

  end #TorqueBox

  # Mixin to all AR models
  ActiveRecord::Base.send(:include, TorqueBox::Messaging::Backgroundable)

end

(gist here)

The main things addressed in the above shim, is stubbing out always_background so that is just ignored when running on MRI, and also short-circuiting out the message processors, such that if you were to say

1
2
queue = fetch('/queues/myqueue')
queue.publish(data)

then when running on MRI it will not try to publish the data to some non-existant queue, but instead figure out which message processor would have been called by Torquebox and just call it directly.

There is now a no-op Gem in the Torquebox repo started by Joe Kutner that came to be after we had set up this initializer, so you might want to check that out as well.