Application Mashing with Rails, Sinatra & Rack

Imagine this: You’re part of the development team working on a fledgling web application. Your application is built on Rails, and (naturally) is incredibly awesome. Because you’re a nice guy (and your best friend is a technical writer), you’ve decided to include a wealth of support documents for your users.

Naturally, your technical writer doesn’t know Rails (otherwise he’d be your programmer, right?), so the responsibility of adding the HTML that he generates to the application has fallen on your shoulders. You have a few choices. You could add all of the HTML to your Rails application (which would lead to a seriously huge project file structure) or you could look for a better solution.

Enter: The Better Solution

Rails is built to play nice with Rack. Rack provides an elegant way to bridge your web server with your Ruby application. It’s also totally awesome.

It’s probably obvious, but Rack also happens to be the solution to our problem. Using Rack::Builder, we can construct a single application entity using any number of Rack applications or middlewares by writing a single “rackup” file. If you’re using Passenger like I am (and you should), then your rackup file is config.ru.

config.ru


DIR = File.dirname(__FILE__)

require 'rubygems'
require 'sinatra'

# Load our applications
require File.join(DIR, '../my_rails_app/config/environment.rb')
require File.join(DIR, '../static_app/server.rb')
require File.join(DIR, '../help_app/server.rb')

# Sinatra config
set :environment, ENV['RACK_ENV']
disable :run, :reload

# Build our application
app = Rack::Builder.new do
	use Rack::CommonLogger
	
	map '/help' do
		run Help::Documents
	end
	
	map '/' do
		use Rails::Rack::Static
		run Rack::Cascade.new([Static::Documents, ActionController::Dispatcher.new])
	end
end.to_app

run app

Our rackup file builds an application that combines our Rails app with our static and help documents. In the example above, Rack will direct requests for ‘/help’ to Help::Documents. It will send all other requests through Rack::Cascade. Help::Documents and Static::Documents are individual Sinatra applications that have a little bit of Sinatra’s magic removed so that we can use something other than the default Sinatra.application to run them.

server.rb


module Static
	class Documents < Sinatra::Base
		set :root, File.dirname(__FILE__)
		set :public, File.join(File.dirname(__FILE__), 'public')
		enable :static
		
		get '/' do
			"Static documents don't need to run on Rails."
		end
	end
end

What’s with this Rack::Cascade business, you ask? Rack::Cascade takes in an array of Rack applications. It will try a request on each of the Rack applications and will return the first response that is not 404. So, if Static::Documents is written to respond to a request to ‘/’, it will effectively replace whatever you have set to run at your Rails root.

Congratulations, you’ve just mashed three separate Ruby applications into one! But we’re not done yet…

Playing in the Same Sandbox

If you’ve been paying attention, you probably noticed is that while we now have three applications running from the same URI, none of them are talking to one another. Our help documents don’t have a clue who is logged into the Rails application and the “site-wide” FlashHash messages are anything but, since we’re only displaying them in our Rails application. Far from ideal if you’re trying to create a seamless user experience.

Let’s fix that. Lucky for us, the fix is as simple as sprinkling some magical middleware on Sinatra.

server.rb


use Rack::Session::Cookie,
	:key => '_rails_session',
	:secret => 'secret_secret_hash'

Rails uses Rack::Session, which means that any Rack application can access the session as long as the :key and :secret are the same. By adding the Rack::Session middleware to our Sinatra application (just copy :key and :secret from your config/environment.rb file), we now have access to the global session variable.

server.rb


@flash = session['flash'] || {}
@flash[:message] = "A message from Sinatra: Your Rails user_id is #{session[:user_id]}"

A Few More Reasons to Rack it Up

Those of you who know me understand that I program in my free time (which is scarce), so I love easy, DRY code. I’m a big fan of keeping projects simple, which is why I love the idea of mashing different applications under the same URI. Rack::Cascade provides a dead simple way to combine applications, giving us the ability to do things that may be cumbersome or less DRY under a single framework.

From a single rackup file, we can:

  • Use the most appropriate framework for the task.
  • Combine work from separate development teams on separate schedules.
  • Integrate previously existing applications.
  • Allow for segmental updates and downtime with separate Capistrano tasks.
  • Manage a large project across multiple Git repos.

If you haven’t sat down and played with Rack, you really should. Especially as Rails 3 is rapidly approaching…

Note: I wrote this article about 4 months ago and never got around to publishing it. I figured someone could still benefit from it, even though Rack has become much more prevalent.

rack   ↓ rails   ↓ tutorial  
  1. torrent-sites reblogged this from yas
  2. speed-up-my-pc reblogged this from yas
  3. free-registry-cleaner reblogged this from yas
  4. foreclosure-listings reblogged this from yas
  5. gma reblogged this from yas
  6. yas posted this