Rails meets Sinatra #2 - Mix n' Match 6

Posted by pratik
on Sunday, November 16

In my previous post, we saw how to mount a sinatra application at a specific location for a regular application. But using Rack::Cascade, we can take that solution to a whole new height. That is :

  • Put sinatra code in any of your regular Rails controllers.
  • No need to mount at Sinatra at a specific URI.
  • Have Sinatra work for any URI, gracefully fallback to Rails if no Sinatra method matches the path.
  • Use your models/libraries etc. in both Rails and Sinatra.

So your controllers can look like :

1
2
3
4
5
6
7
8
class HomeController < ApplicationController
  get '/' do
    Item.count.to_s
  end
  
  def index
  end
end

and even :

1
2
3
4
5
6
7
8
9
class SiteController < ApplicationController
  get '/:name' do
    Site.find_by_name(params[:name]).html
  end

  def new
  end

end

And environment.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
RAILS_GEM_VERSION = '2.2.0' unless defined? RAILS_GEM_VERSION
require File.join(File.dirname(__FILE__), 'boot')

Rails::Initializer.run do |config|
  config.gem "sinatra"
  config.time_zone = 'UTC'
  config.action_controller.session = { :session_key => '_monkey_session',  :secret => 'whatever' }
end

set :env, :production
disable :run, :reload

# Remove trailing slash from URIs reaching Sinatra
before { request.env['PATH_INFO'].gsub!(/\/$/, '') if request.env['PATH_INFO'] != '/' }

# Checking AR Connections back to the pool
after { ActiveRecord::Base.clear_active_connections! }

# Preload controllers with Sinatra code
require 'home_controller'
require 'site_controller'

And most important, the rack setup file :

1
2
3
4
5
6
7
8
9
require File.dirname(__FILE__) + '/config/environment'
require 'thin'
  
app = Rack::Builder.new {
  use Rails::Rack::Static
  run Rack::Cascade.new([Sinatra.application, ActionController::Dispatcher.new])
}.to_app
 
Rack::Handler::Thin.run app, :Port => 3000, :Host => "0.0.0.0"

And start your server !

[lifo@219 monkey]$ ruby racked.rb

Rails meets Sinatra 11

Posted by pratik
on Monday, November 10

For real !

It was pleasantly simple to get Rails + Sinatra run in the same process.

First of all, put your Sinatra application inside RAILS_ROOT. My Sinatra app is called tiny :

1
2
3
4
5
6
7
8
# RAILS_ROOT/tiny.rb
require 'sinatra'

before { request.env['PATH_INFO'].gsub!(/\/$/, '') }

get '' do
  'Hello Sinatra!'
end

And then do the rack dance – racked.rb :

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
# RAILS_ROOT/racked.rb
require File.dirname(__FILE__) + '/config/environment'
require 'thin'
 
# Sinatra stuff
require 'tiny'
 
# Make sinatra play nice
set :env, :production
disable :run, :reload
 
app = Rack::Builder.new {
  use Rails::Rack::Static
 
  # Anything urls starting with /tiny will go to Sinatra
  map "/tiny" do
    run Sinatra.application
  end
 
  # Rest with Rails
  map "/" do
    run ActionController::Dispatcher.new
  end
}.to_app
 
Rack::Handler::Thin.run app, :Port => 3000, :Host => "0.0.0.0"

Line 25 ensures your Sinatara application is used for all the urls starting from /tiny. Any url not starting from /tiny will go to the Rails application.

Now start your server :

$ ruby racked.rb

If you go to http://0.0.0.0:3000/tiny, you’ll be greeted with ‘Hello from sinatra!’. And your Rails application will continue to work as expected for everything else. Voila! There you have the fastest ‘hello world’ Ruby framework embedded with Rails :)

Please note that Rails’ built in rack adapter isn’t perfect and you might find a glitch or two. All in good time

UPDATE 1: Prettify Sinatra code and remove monkey patches, thanks to the feedback from Blake Mizerany. Also add a before filter to sinatra app for removing trailing slash.